Skip to content

Example of Node app using Sequelize with relations

Introduction

This is an example of a Node and Express application using Sequelize. The app implements CRUD operations for two models artists and songs. There is a relationship between these two. Songs have one artist and artists can have multiple songs

GitHub repository

In this repository https://github.com/obravocedillo/Sequelize-Express-Test-Project-CRUD you can find the code.

Npm modules

This is a list of all the packages needed and the reason why they were installed

"dotenv" (npm install dotenv --save): Dotenv is a zero-dependency module that loads environment variables from a .env file into process.env.
"dotenv-flow" (npm install dotenv-flow): Allows to load variables from .env files, we can have multiple env files depending of the environment we are
using, for example development and production
"sequelize" (npm i sequelize): Sequelize is an easy-to-use and promise-based Node.js ORM tool for Postgres, MySQL, MariaDB, SQLite, DB2, Microsoft SQL Server, and Snowflake
"mysql2" (npm install --save mysql2): MySQL client for Node.js with focus on performance.
"express" (npm install express): Simplifies the creation of RESTful APIs
using Node
"express-async-errors" (npm install express-async-errors --save): A dead simple ES6 async/await support hack for ExpressJS
"nodemon" (npm install --save-dev nodemon): Nodemon is a tool that helps develop Node.js based applications by automatically restarting the node application when file changes in the directory are detected.

Env variables

Env variables are loaded from a file called .env on the root of the project, add this variables before running the project you can use your own database and port, because we use dotenv-flow we can have multiple files depending on the environment for example development.env and production.env

# /.env

PORT=3015

DATABASE_DIALECT=mysql
DATABASE_HOST=databasehost
DATABASE_PORT=3306
DATABASE_USERNAME=databaseusername
DATABASE_PASSWORD=databasepassword
DATABASE_NAME=songs

Constants

Constants are variables that wont change and are reused on multiple parts of our application

// ./constants/server.js

const HTTP_SUCCESS_CODE = 200;
const HTTPS_ERROR_CODE = 500;

module.exports = {
  HTTP_SUCCESS_CODE,
  HTTPS_ERROR_CODE,
};

Loaders

Loaders are functions used to initialize external services before the application starts, the application used two loaders one to initialize Sequelize and create a variable we can import and use on other parts of the application and another one to initialize and express app, we also have a function that initializes all loaders and makes the express app listen for incoming traffic

// .loaders/index.js

// Initialize project
const { initializeExpress } = require("./express");
const { initializeSequelize, getSequelize } = require("./sequelize");

// Port that will be uses to listen to requests
const port = process.env.PORT || 3015;

const initializeApp = async (app) => {
  // Initialize sequelize
  await initializeSequelize();

  // Initialize express
  const initializedApp = initializeExpress(app);

  /**
  * Create sequelize tables, important use after
  * initializeSequelize and -initializeExpress so sequelize instance
  * is defined and models are already importes by routes imported on
  * initializeExpress
  **/
  const sequelize = getSequelize();
  await sequelize.sync();

  console.log("Database synched");

  initializedApp.listen(port, () => {
    console.log(`App listening on port ${port}!`);
  });

  return app;
};

module.exports = {
  initializeApp,
};

Middlewares

Middlewares are middle function that run before a request reaches a controller, the project has one middleware in charge of handle async error and avoid the app to crash

// ./middleware/error.js

const { HTTPS_ERROR_CODE } = require("../constants/server");

const handleErrorsMiddleware = (err, req, res, next) => {
  // Default error message
  let errorMessage = "Internal server error please contact support";

  // Check that headers were not already sent, to avoid "Can't set headers after they are sent to the client" error
  if (!res.headersSent) {
    res
      .status(HTTPS_ERROR_CODE)
      .send({ msg: `${errorMessage}: ${err.message}` });
  }

  next();
};

module.exports = {
  handleErrorsMiddleware,
};

Routes

Routes handle incoming requests, we define the routes and methods available in our application, send request to specific controller depending on the route and method

// ./routes/index.js

const express = require("express");

const { HTTP_SUCCESS_CODE } = require("../constants/server");

const songsRouter = require("./songs");
const artistsRouter = require("./artists");

const mainRouter = express.Router();

mainRouter.get("/health", (req, res) => {
  res.status(HTTP_SUCCESS_CODE).send("Working");
});

mainRouter.use("/songs", songsRouter);
mainRouter.use("/artists", artistsRouter);

module.exports = mainRouter;

Controllers

Controllers are in charge of validating requests, calling the correct services to execute the logic needed and returning a response to the client depending on the outcome

// ./controllers/artists.js

const { HTTP_SUCCESS_CODE, HTTPS_ERROR_CODE } = require("../constants/server");
const { artistsService } = require("../services/artists");

const artistsController = {
  getArtistsController: async (req, res, next) => {
    const artists = await artistsService.getAllArtists();

    res.status(HTTP_SUCCESS_CODE).send(artists);
  },
  saveArtistController: async (req, res, next) => {
    const { name } = req.body;

    if (!name) {
      res.status(HTTP_SUCCESS_CODE).send("Artist information is required");
    }

    const savedArtist = await artistsService.saveArtist({ name });

    res.status(HTTP_SUCCESS_CODE).send(savedArtist);
  },
  updateArtistController: async (req, res, next) => {
    const { artistId, artist } = req.body;

    if (!artistId || !artist) {
      res.status(HTTPS_ERROR_CODE).send("Artist information is required");
    }

    const updatedArtist = await artistsService.updateArtist(artistId, artist);

    res.status(HTTP_SUCCESS_CODE).send({ msg: "Success" });
  },
  deleteArtistController: async (req, res, next) => {
    const { name } = req.body;

    if (!name) {
      res.status(HTTPS_ERROR_CODE).send("Artist information is required");
    }

    const deletedArtist = await artistsService.deleteArtist(name);

    res.status(HTTP_SUCCESS_CODE).send({ msg: "Success" });
  },
};

module.exports = { artistsController };

Services

Services contain reusable logic used on our application, is the place where we store our bussines logic

// ./services/artists.js

const { artist } = require("../models/artist");

const artistsService = {
  getAllArtists: async () => {
    const artists = await artist.findAll({ include: "songs" });

    return artists;
  },

  saveArtist: async (artistInformation) => {
    const newArtist = await artist.create(artistInformation);

    return newArtist;
  },

  updateArtist: async (artistId, artistInformation) => {
    const updatedArtist = await artist.update(
      { ...artistInformation },
      {
        where: {
          id: artistId,
        },
      },
    );

    return updatedArtist;
  },

  deleteArtist: async (name) => {
    const deletedArtist = await artist.destroy({
      where: {
        name,
      },
    });

    return deletedArtist;
  },
};

module.exports = {
  artistsService,
};

Models

Models are the essence of Sequelize. A model is an abstraction that represents a table in your database, is our way to interact with the database

// ./models/artist.js

const { DataTypes, HasMany } = require("Sequelize");

const { getSequelize } = require("../loaders/sequelize");
const { song } = require("./song");

const sequelize = getSequelize();

const artist = sequelize.define("artist", {
  name: {
    type: DataTypes.STRING,
    allowNull: false,
  },
});

song.belongsTo(artist, { as: "artist" });
artist.hasMany(song, { as: "songs" });

module.exports = {
  artist,
};

Initializing server

This the entry point of our project here we initialize our app using our loaders

// ./index.js

require("dotenv-flow").config();
require("express-async-errors");

const express = require("express");

const { initializeApp } = require("./src/loaders");

const app = express();

initializeApp(app).catch((err) => {
  console.log(err);
  process.exit(1);
});

Run project

Clone repository install dependencies using "npm install" add your own env variables on a .env file on the root of the project and use "npm run dev"