简体   繁体   中英

Why does Sequelize “include / attributes” won't work with findAll and findOne requests

I'm working on an API communicating with MySQL via Sequelize. I'm dealing with users and their publications. This works perfectly fine, I can cerate users and publications with userId associated. But now I want to get the name of the user who did the publication, and not just the userId.

So I work with associations between my models 'Users' and 'Publications'. I've defined the associations in my sequelize models and migration files but when I add to my controller :

            model : User,
            attributes: ['firstName']
        }],

I get an error 400 with no details.

If I only write "model : user" without attributes it does not work either so I guess it cannot find the model User.

I followed the steps explained by Sequelize manual, tried to compare with other github examples and it seems ok in theory. I spent two days trying to changes character case, I tried many options given by sequelize here , I dropped all my tables and recreated them but I'm running in circles now :-)

Below my migration files and models. Thanks for your help !

Controller for Publications

const models = require('../models');        // importation des modèles sequelize
const Publication = models.publication;
const User = models.user;

exports.getAllPublications = (req, res, next) => {
    Publication.findAll({
        include: [{
            model : User,
            attributes: ['firstName']
        }],
        order: [['createdAt', 'DESC']]
    })

    .then(        
        (publications) => {        
            res.status(200).json(publications);
        }
    ).catch(
        (error) => {
            res.status(400).json({   
                error: error
            });
        }
    );
};

User Sequelize model

'use strict';
const {
  Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
    class User extends Model {
      static associate(models) {
        models.User.hasMany(models.Publication)

      }
    };

  User.init({
    firstName: DataTypes.STRING,
    lastName: DataTypes.STRING,
    email: DataTypes.STRING,
    password: DataTypes.STRING,
    isAdmin: DataTypes.BOOLEAN
  }, {
    sequelize,
    modelName: 'User',
  });
  return User;
};

Publication Sequelize model

'use strict';
const {
  Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
  class Publication extends Model {
    static associate(models) {
      models.Publication.belongsTo(models.User, {
        foreignKey: {
      
          allowNull: false,
        }
        });
    }
  };
  
  Publication.init({
    userId: DataTypes.INTEGER,
    title: DataTypes.STRING,
    content: DataTypes.STRING
  }, {
    sequelize,
    modelName: 'Publication',
  });
  return Publication;
};

Index

'use strict';

const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(__filename);
require('dotenv').config()               // importation dotenv pour sécuriser passwords
const db = {};


let sequelize;
  sequelize = new Sequelize(
    process.env.DB_NAME, 
    process.env.DB_USER, 
    process.env.DB_PASSWORD,  {

      host: process.env.DB_HOST,
    dialect: process.env.DB_DIALECT
  });

fs
  .readdirSync(__dirname)
  .filter(file => {
    return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
  })
  .forEach(file => {
    const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes);


    db[model.name] = model;
  });

Object.keys(db).forEach(modelName => {
  if (db[modelName].associate) {
    db[modelName].associate(db);
  }
});

db.sequelize = sequelize;
db.Sequelize = Sequelize;

db.user = require("./user.js")(sequelize, Sequelize)
db.publication = require("./publication.js")(sequelize, Sequelize)
db.comment = require("./comment.js")(sequelize, Sequelize)

module.exports = db

Publication migration file

'use strict';
module.exports = {
  up: async (queryInterface, Sequelize) => {
    await queryInterface.createTable('Publications', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      
      userId: {
        allowNull: false,
        type: Sequelize.INTEGER,
        references: {
          model: 'Users',
          key: 'id'
        }
      },
      title: {
        allowNull: false,
        type: Sequelize.STRING,
        len: [2,50]
      },
      content: {
        allowNull: false,
        type: Sequelize.STRING(1234),
        len: [2,1000]
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  down: async (queryInterface, Sequelize) => {
    await queryInterface.dropTable('Publications');
  }
};

User creation controller

exports.signup = (req, res, next) => {
    
  bcrypt.hash(req.body.password, 10)    // On crypte le mot de passe (algorithme exécuté 10 fois) / asynchrone
  
        .then(hash => {   
          console.log(hash)
            const newUser = User.create({           // modèle sequelize
              lastName : req.body.lastName,
              firstName : req.body.firstName,
              email: req.body.email,
              password: hash,                  // On enregistre le mdp crypté plutôt que le mdp simple
             
              })
              

        .then((newUser )=> res.status(201).json({ message: 'created' })) // Requête traitée avec succès et création d’un document
        .catch(error => 
          res.status(400).json({ error })); // Bad Request*/
        
      })
    .catch(error => { 
      console.log("erreur 500")
      res.status(500).json({ error })}); // Erreur interne du serveur
  };

New App.js after changing models as recommended

'use strict'

const express = require('express');         // importation application Express
require('dotenv').config()                  // importation dotenv pour sécuriser passwords
const mysqlTable = process.env.MYSQLTABLE;
const mysqlUsername = process.env.MYSQLUSERNAME;     
const mysqlPassword = process.env.MYSQLPASSWORD; 
const rateLimit = require("express-rate-limit");
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100 // limit each IP to 100 requests per windowMs
});

const bodyParser = require('body-parser');  // importation fonction body parser pour extraction objet json de la demande
const cors = require('cors');               // module CORS
const { Sequelize } = require('sequelize'); // importation application Sequelize pour communiquer avec MySQL
    
const userRoutes = require('./routes/user');   // Importation routeur users
const publicationRoutes = require('./routes/publication');   // Importation routeur posts
const commentRoutes = require('./routes/comment');   // Importation routeur posts

const Publication = require('./models/publication');
const User = require('./models/user')
Publication.belongsTo(User, { Constraints: true, onDelete: 'CASCADE'});
User.hasMany(Publication);

const sequelize = new Sequelize(mysqlTable, mysqlUsername, mysqlPassword, { // Connexion à la base de données mySQL
  host : 'localhost',
  dialect: 'mysql'
  })

const app = express();      // application Express
app.use(bodyParser.json()); // Enregistrement body parser
app.use(cors());            // module CORS
app.use(limiter);           // rate limit

app.use((req, res, next) => {  // Ajout headers pour résoudre les erreurs CORS
    res.setHeader('Access-Control-Allow-Origin', '*'); // accéder à notre API depuis n'importe quelle origine
    res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content, Accept, Content-Type, Authorization'); // ajouter les headers mentionnés aux requêtes envoyées vers notre API
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS'); // envoyer des requêtes avec les méthodes mentionnées 
    next();
  });


app.use('/api/auth', userRoutes)      // Enregistrement routeur users
app.use('/api/publication', publicationRoutes)    // Enregistrement routeur publications
app.use('/api/comment', commentRoutes)    // Enregistrement routeur publications
module.exports = app;

As I understood from our exchange of comments, the problem comes from the declaration of associations.

Personally I don't like associations to be defined like what you have done (I didn't say it's incorrect!), personally I do it like following.

I create an instance of Sequelize under a specific file (for example, under ./util/database.js):

database.js :

const Sequelize = require('sequelize');
const sequelize = new Sequelize('database_name', 'user_name', 'password', {
  dialect: 'mysql',
  storage: "./session.mysql",
  host: 'localhost'
});

module.exports = sequelize;

I define my models like this:

User model :

const Sequelize = require('sequelize');
const sequelize = require('../util/database');

const User = sequelize.define('user', {
  id: {
    type: Sequelize.INTEGER,
    autoIncrement: true,
    allowNull: false,
    primaryKey: true
  },
  firstName: Sequelize.STRING,
  lastName: Sequelize.STRING,
  email: Sequelize.STRING,
  password: Sequelize.STRING,
  isAdmin: Sequelize.BOOLEAN
});

module.exports = User;

Publication model :

const Sequelize = require('sequelize');
const sequelize = require('../util/database');

const Publication = sequelize.define('publication', {
      id: {
        type: Sequelize.INTEGER,
        autoIncrement: true,
        allowNull: false,
        primaryKey: true
      },
      title: Sequelize.STRING,
      content: Sequelize.STRING
    });
    
    module.exports = Publication;

Then, I define the associations in the app.js file:

Publication.belongsTo(User, { Constraints: true, onDelete: 'CASCADE'});
User.hasMany(Publication);

To be sure that the new database's schema is well integrated, I use the sync method with force attribute (just for the first time, then you should delete this attribute and just use the sync method)

// this will run on starting the server
sequelize
  .sync({ force: true }) // run it just in the first time after changing the database, this command will re-draw the database
  // .sync()
  .then(() => app.listen(8080))
  .catch(err => console.log(err));

And finally, you can keep what you have done to fetch the data, and also you can do something like this:

User.findByPk(userId, {include: ['publications']})

//supposing that you have an instance of a User (user)
user.getPublications({})
user.getPublications({where: { id: publicationId }})

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM