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
In this repository https://github.com/obravocedillo/Sequelize-Express-Test-Project-CRUD you can find the code.
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 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 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 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 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 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 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 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 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,
};
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);
});
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"