I am new in the relational database. I am using node js and express for the backend, REST API and database is Postgresql. I am using Sequelize for connection and models.I have created two models, one is a student and another is a course. My goal is One student can have multiple courses and want to prevent duplicate student name, phone, email. I successfully connect to the database and able to post,get,update, delete the both student and course model. From testing the app I am using Postman. But I am not sure, am I doing right for the relationship between student and course. And also I don't how to display the relationship between two tables. I will be really glad if anyone helps me out.
I want to display my like this:
{
"students": [
{
"id": 1,
"name": "Anni Anonen",
"birthday": "1992-02-28",
"address": "Kivakatu 1",
"zipcode": "00500",
"city": "Helsinki",
"phone": "+358506760702",
"email": "anni.anonen#testing.fi",
"courses": [1,2,3] //SHOW COURSES LIKE THIS
},
{
"id": 2,
"name": "Ville Anonen",
"birthday": "2000-03-28",
"address": "Hämeentie 1",
"zipcode": "00510",
"city": "Helsinki",
"phone": "+358508660702",
"email": "ville.anonen#testing.fi",
"courses": [3]
},
{
"id": 3,
"name": "Tapani Kumpu",
"birthday": "1999-05-28",
"address": "Jokukatu 17",
"zipcode": "00560",
"city": "Helsinki",
"phone": "+358502330702",
"email": "tapani.kumpu#testing.fi",
"courses": [1,4]
}
],
"courses": [
{
"id": 1,
"name": "Gymnastics 1",
"startdate": "2020-01-01",
"enddate": "2020-02-10"
},
{
"id": 2,
"name": "Gymnastics 2",
"startdate": "2020-01-01",
"enddate": "2020-02-10"
},
{
"id": 3,
"name": "Fitness 1",
"startdate": "2020-02-01",
"enddate": "2020-02-20"
},
{
"id": 4,
"name": "Dance 1",
"startdate": "2020-05-01",
"enddate": "2020-05-02"
}
]
}
This is my models
const sequelize = require("sequelize");
var con = new sequelize("school", "postgres", "password", {
host: "localhost",
dialect: "postgres",
pool: {
max: 5,
min: 0,
idle: 10000
}
});
const Student = con.define("student", {
name: {
type: sequelize.STRING,
allowNull: false
},
birthday: {
type: sequelize.DATEONLY,
allowNull: false
},
address: {
type: sequelize.STRING,
allowNull: false
},
zipcode: {
type: sequelize.INTEGER,
allowNull: false
},
city: {
type: sequelize.STRING,
allowNull: false
},
phone: {
type: sequelize.BIGINT,
allowNull: false
},
email: {
type: sequelize.STRING,
allowNull: false,
validate: {
isEmail: true
}
}
});
const Course = con.define("course", {
id: {
type: sequelize.INTEGER,
primaryKey: true
},
name: { type: sequelize.STRING },
startdate: { type: sequelize.DATEONLY },
enddate: { type: sequelize.DATEONLY },
studentId: { type: sequelize.INTEGER, foreignKey: true }
});
Student.hasMany(Course);
Course.belongsTo(Student);
//con.sync({ force: true });
module.exports = { Student, Course };
This is express server
require("dotenv").config();
const express = require("express");
const app = express();
const morgan = require("morgan");
const helmet = require("helmet");
const cors = require("cors");
const { Student, Course } = require("./db");
//app middlewear
app.use(morgan("common"));
app.use(helmet());
app.use(cors());
app.use(express.json()); //body Parser
//student
app.get("/students", async (req, res, next) => {
try {
await Student.findAll().then(docs => {
const response = {
count: docs.length,
students: docs
};
res.json(response);
});
} catch (error) {
console.log(error);
}
});
app.get("/students/:id", async (req, res, next) => {
const id = req.params.id;
try {
Student.findByPk(id).then(data => {
console.log(data);
res.json(data);
});
} catch (error) {
console.log(error);
}
});
app.put("/students/:id", async (req, res) => {
const id = req.params.id;
const update = req.body;
try {
await Student.update(update, { where: { id } }).then(data => {
res.json(data);
});
} catch (error) {
console.log(error);
}
});
app.delete("/students/:id", async (req, res, next) => {
const id = req.params.id;
try {
Student.destroy({ where: { id } }).then(data => {
res.json(data);
});
} catch (error) {
console.log(error);
}
});
app.post("/students", async (req, res, next) => {
try {
const logs = new Student(req.body);
const entry = await logs.save();
res.json(entry);
} catch (error) {
if (error.name === "ValidationError") {
res.status(422);
}
next(error);
}
});
//course
app.get("/courses", async (req, res, next) => {
try {
await Course.findAll().then(docs => {
const response = {
count: docs.length,
courses: docs
};
res.json(response);
});
} catch (error) {
console.log(error);
}
});
app.get("/courses/:id", async (req, res, next) => {
const id = req.params.id;
try {
Course.findByPk(id).then(data => {
console.log(data);
res.json(data);
});
} catch (error) {
console.log(error);
}
});
app.put("/courses/:id", async (req, res, next) => {
const id = req.params.id;
const update = req.body;
try {
await Course.update(update, { where: { id } }).then(data => {
res.json(data);
});
} catch (error) {
console.log(error);
}
});
app.delete("/courses/:id", async (req, res, next) => {
const id = req.params.id;
try {
Course.destroy({ where: { id } }).then(data => {
res.json(data);
});
} catch (error) {
console.log(error);
}
});
app.post("/courses", async (req, res, next) => {
try {
const logs = new Course(req.body);
const entry = await logs.save();
res.json(entry);
} catch (error) {
if (error.name === "ValidationError") {
res.status(422);
}
next(error);
}
});
const port = process.env.PORT || 5000;
app.listen(port, () => console.log(`🚀 App is listening at port ${port}!`));
Currently I have some models. I'm using graphql with dataloader-sequelize and it works fine as long as I show associated tables without third level.
My models:
"articulo.js"
'use strict';
module.exports = (sequelize, DataTypes) => {
const Articulo = sequelize.define(
'articulos',
{
art_codigo: {
type: DataTypes.INTEGER,
primaryKey: true,
unique: true,
autoIncrement: true
},
art_nombre: DataTypes.STRING(255),
art_longitud: DataTypes.STRING(250),
art_latitud: DataTypes.STRING(250),
.....[more columns]
art_contenido: DataTypes.TEXT,
},
{
timestamps: false,
freezeTableName: true,
name: {
singular: 'Articulo',
plural: 'Articulos',
},
indexes: [
{
unique: true,
fields: ['art_codigo'],
},
],
}
);
Articulo.associate = (models) => {
Articulo.belongsTo(models.canalizados,
{
foreignKey: 'art_canalizado',
as:"Canalizado",
}
);
Articulo.belongsTo(
models.articulos_tipos,
{
foreignKey: 'art_tipo'
}
);
};
return Articulo;
};
articulo_tipo.js
'use strict';
module.exports = (sequelize, DataTypes) => {
const ArticuloTipo = sequelize.define('articulos_tipos', {
ari_codigo: {
type: DataTypes.INTEGER,
primaryKey: true,
unique: true,
autoIncrement: true
},
ari_nombre: DataTypes.STRING(255),
}, {
timestamps: false,
freezeTableName: true,
name: {
singular: 'ArticuloTipo',
plural: 'ArticulosTipos',
},
indexes: [
{
unique: true,
fields: ['ari_codigo'],
},
],
});
ArticuloTipo.associate = (models) => {
ArticuloTipo.hasMany(models.articulos)
};
return ArticuloTipo;
};
canalizado.js
'use strict';
module.exports = (sequelize, DataTypes) => {
const Canalizado = sequelize.define('canalizados', {
cnl_codigo: {
type: DataTypes.INTEGER,
primaryKey: true,
unique: true,
autoIncrement: true
},
cnl_fecha_alta: DataTypes.DATE,
...... [more columns]
cnl_revisado: DataTypes.BOOLEAN,
}, {
timestamps: false,
freezeTableName: true,
name: {
singular: 'Canalizado',
plural: 'Canalizados',
},
indexes: [
{
unique: true,
fields: ['cnl_codigo'],
},
],
}
);
Canalizado.associate = (models) => {
Canalizado.hasMany(models.articulos);
Canalizado.belongsTo(
models.canalizados_tipos,
{
foreignKey: 'cnl_tipo',
}
);
};
return Canalizado;
};
canalizado_tipo.js
'use strict';
module.exports = (sequelize, DataTypes) => {
const CanalizadoTipo = sequelize.define('canalizados_tipos', {
cai_codigo: {
type: DataTypes.INTEGER,
primaryKey: true,
unique: true,
autoIncrement: true
},
cai_nombre: DataTypes.STRING(50)
}, {
timestamps: false,
freezeTableName: true,
tableName: "canalizados_tipos",
name: {
singular: 'CanalizadoTipo',
plural: 'CanalizadosTipo',
},
indexes: [
{
unique: true,
fields: ['cai_codigo'],
},
],
});
CanalizadoTipo.associate = (models) => {
CanalizadoTipo.hasMany(models.canalizados)
};
return CanalizadoTipo;
};
My resolvers:
articulo.js
const Sequelize = require('sequelize');
const {detectarCampos} = require('../_extra/comunes'); //Optimize which columns you want to use in graphql
const Op = Sequelize.Op;
const resolvers = {
Articulo:{
art_tipo: (parent, args, { models, options }, info) => {
return parent.getArticuloTipo(options); //It's an internal getter from sequelize, isn't it?
},
art_canalizado: (parent, args, { models, options }, info) => {
return parent.getCanalizado(options); //It's an internal getter from sequelize, isn't it?
},
},
Query: {
async getArticulo(root, { codigo }, { models }, info) {
return models.articulos.findByPk(
codigo,
{attributes: detectarCampos(info),}
);
},
async getArticulos(root, { nombre, tipo}, { models, options }, info) {
var whereStatement = {};
if(nombre){
whereStatement.art_nombre = {[Op.like]: '%' + nombre + '%'};
}
if (tipo){
whereStatement.art_tipo = tipo;
}
return models.articulos.findAll({
attributes: detectarCampos(info),
where: whereStatement,
//limit: 10,
options
});
},
async getAllArticulos(root, args, { models }, info) {
return models.articulos.findAll( {
attributes: detectarCampos(info),
limit: 10,
});
},
},
Mutation: {
},
}
module.exports = resolvers
canalizado.js
const {detectarCampos} = require('../_extra/comunes');
const resolvers = {
Canalizado:{
cnl_tipo: (parent, args, { models, options }, info) => {
return parent.getCanalizadoTipo(options)
},
},
Query: {
async getCanalizado(root, { codigo }, { models, context }, info) {
return await models.canalizados.findByPk(codigo,
{attributes: detectarCampos(info),});
},
async getCanalizados(root, { tipo }, { models, options }, info) {
var whereStatement = {};
if (tipo)
whereStatement.cnl_tipo = tipo;
return models.canalizados.findAll({
attributes: detectarCampos(info),
where: whereStatement,
limit: 2,
options
});
},
async getAllCanalizados(root, args, { models, options }) {
return models.canalizados.findAll({
attributes: detectarCampos(info),
limit: 100,
options
});
},
},
Mutation: {
},
}
module.exports = resolvers
It works fine if I search in graphql with this sentence:
query{
getArticulos(tipo:2){
art_codigo
art_nombre
art_tipo{
ari_nombre
}
art_latitud
art_longitud
}
}
Executing (default): SELECT [art_codigo], [art_nombre], [art_tipo], [art_latitud], [art_longitud] FROM [articulos] AS [articulos] WHERE [articulos].[art_tipo] = 2;
Executing (default): SELECT [ari_codigo], [ari_nombre] FROM [articulos_tipos] AS [articulos_tipos] WHERE [articulos_tipos].[ari_codigo] IN (2);
On the other hand, if I try to look for in a deeper level, I get automatic names from columns I don't need to use:
query{
getArticulos(tipo:2){
art_codigo
art_nombre
art_tipo{
ari_nombre
}
art_canalizado{
cnl_codigo
}
art_latitud
art_longitud
}
}
Executing (default): SELECT [art_codigo], [art_nombre], [art_tipo], [art_latitud], [art_longitud] FROM [articulos] AS [articulos] WHERE [articulos].[art_tipo] = 2;
Executing (default): SELECT [ari_codigo], [ari_nombre] FROM [articulos_tipos] AS [articulos_tipos] WHERE [articulos_tipos].[ari_codigo] IN (2);
Executing (default): SELECT [cnl_codigo], [cnl_fecha_alta], [........], [cnl_revisado], [cnl_tipo], [cnl_fuente], [cnl_autor], [CanalizadoTipoCaiCodigo] FROM [canalizados] AS [canalizados] WHERE [canalizados].[cnl_codigo] IN (51357, 51365, 51379, [........], 63910);
In this case, in Graphql returns this error:
"message": "Invalid column name 'CanalizadoTipoCaiCodigo'.",
How can I ommite that field?? Could I use something like "attributes" to specify which attributes I'd like to show?? I tried to use it in resolvers, models... but always with no success
This error is the same if I look for a deep level:
query{
getArticulos(relevancia:2){
art_codigo
art_nombre
art_tipo{
ari_nombre
}
art_canalizado{
cnl_codigo
cnl_tipo{
cai_nombre
}
}
art_latitud
art_longitud
}
}
Any idea about my problem? Everrything is wellcome!!
UPDATE
server.js
const express = require('express');
const { ApolloServer } = require('apollo-server-express');
const typeDefs = require('./configuracion/schema/typeDefs')
const resolvers = require('./configuracion/schema/resolvers')
const models = require('./configuracion/models')
const { createContext, EXPECTED_OPTIONS_KEY } = require('dataloader-sequelize');
const dataloaderContext = createContext(models.sequelize);
//const server = new ApolloServer({ typeDefs, resolvers, context: { models } });
const server = new ApolloServer({
typeDefs,
resolvers,
context: async () => ({
models,
options: { [ EXPECTED_OPTIONS_KEY ]: dataloaderContext },
}),
});
const app = express();
server.applyMiddleware({ app });
models.sequelize.authenticate().then((err) => {
console.log('*** MSG [server.js]: Successful Connection');
})
.catch((err) => {
console.log('*** ERROR [server.js]: No ha sido posible conectarse a la base de datos', err);
})
//models.sequelize.sync();
app.listen({ port: 3000 }, () =>
console.log(`** API ready at http://localhost:3000${server.graphqlPath} `)
);
configuracion/models/index.js
'use strict';
const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(__filename);
//const env = process.env.NODE_ENV || 'development';
const config = require('../config_sqlserver')
const db = {};
const sequelize = new Sequelize(config.db_database, config.db_user, config.db_password,
{
host: config.db_host,
port: config.DB_PORT, // <----------------The port number you copied
dialect: "mssql",
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000
}
}
);
fs
.readdirSync(__dirname)
.filter(file => {
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
})
.forEach(file => {
//const model = sequelize['import'](path.join(__dirname, file));
const model = sequelize.import(path.join(__dirname, file));
db[model.name] = model;
});
Object.keys(db).forEach(modelName => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;
resolver > articulo_tipo.js
const Sequelize = require('sequelize');
const {detectarCampos} = require('../_extra/comunes');
const Op = Sequelize.Op;
const resolvers = {
Query: {
async getArticuloTipo(root, { codigo }, { models, context }, info) {
return await models.articulos_tipos.findByPk(codigo, { attributes: detectarCampos(info)},);
},
async getArticulosTipos(_, { nombre, tipo }, { models }, info) {r
var whereStatement = {};
if(nombre)
whereStatement.ari_nombre = {[Op.like]: '%' + nombre + '%'};
if(tipo)
whereStatement.ari_codigo = tipo;
return models.articulos_tipos.findAll({
attributes: detectarCampos(info),
where: whereStatement,
});
},
async getAllArticulosTipos(root, args, { models }) {
return models.articulos_tipos.findAll()
},
},
Mutation: {
},
}
module.exports = resolvers
I don't use sequelize ... but I probably can point you in the right direction:
attributes are used already...
maybe not exactly the way you need ...
check what is returned from detectarCampos(info) in resolvers
Probably you'll find that info is undefined ... sometimes info is missing... why!?
art_canalizado: (parent, args, { models, options }, info) => {
return parent.getCanalizado(options); //It's an internal getter from sequelize, isn't it?
},
getCanalizado is called with options while normally it should be called with more arguments:
async getCanalizado(root, { codigo }, { models, context }, info) {
Fix:
Pass missging arguments - it should work (if detectarCampos works, of course).
I am confused. I have the following javascript file:
module.exports = {
connection: 'freshairMysqlServer',
tableName: 'Accounts',
autoCreatedAt: false,
autoUpdatedAt: false,
autoPK: false,
attributes: {
idAccounts: {
type: 'integer',
primaryKey: true
},
AccountName: {
type: 'string'
},
idOrganization: {
type: 'integer'
}
},
GetAccounts: function (page, cb) {
Accounts.query('SELECT Accounts.AccountName as AccountName,' +
' Organizations.Name as Organization FROM Accounts' +
' JOIN Organizations on Accounts.idOrganization = Organizations.idOrganizations',
function (err, results) {
if (err) cb(err)
else cb(null, results);
});
}
}
The module exports a javascript object, {connection: ..., tableName: ..., ...}. This value is used by Sails to extend a Waterline model object.
What is the TypeScript code that accomplishes the same? I have tried several variations but don't seem to be getting it. Help! Thanks.
var objectToExport = {
connection: 'freshairMysqlServer', ... };
export = objectToExport;
and be sure to compile with --module commonjs
My node.js client looks like this:
var MongoClient = require('mongodb').MongoClient;
MongoClient.connect(mongoendpoint, function(err, db) {
if(err) throw err;
var collection = db.collection('test-collection');
var ws = new WebSocket(websocket_Endpoint);
ws.on('open', function() {
log.info('Connected.');
});
ws.on('message', function(data, flags) {
wsevent = JSON.parse(data);
var args = {
'query': {
id: '1.2.3.4'
},
'update': {
$set: {
lastseen: "201405231344"
},
$addToSet: {
record: "event123"
}
},
'new': true,
'upsert': true
};
collection.findAndModify(args, function(err, doc){
log.info(err);
});
});
});
When I run this, I get the following error:
info: name=MongoError, ok=0, errmsg=need remove or update
I can't figure out why. I can run the exact same args json above using RoboMongo and the query works just fine.
Robomongo Query
db['test-collection'].findAndModify({"query":{"id":"1.2.3.4"},"update":{"$setOnInsert":{"lastseen":"201405231344"},"$addToSet":{"record":"event123"}},"new":true,"upsert":true});
What am I missing?
Your args section is wrong, it should be an array and does not need key values for "query" and "update". And the "options" value also needs to be an object (sub-document):
var args = [
{ id: '1.2.3.4' },
{
$set: {
lastseen: "201405231344"
},
$addToSet: {
record: "event123"
}
},
{
'new': true,
'upsert': true
}
];
Or specifically in the call:
collection.findAndModify(
{ id: '1.2.3.4' },
{
$set: { lastseen: "201405231344" },
$addToSet: { record: "event123" }
},
{
'new': true,
'upsert': false
},
function(err, doc){
Examples are also included on the manual page.