DEV Community

Cover image for JavaScript CRUD Rest API using Nodejs, Express, Sequelize, Postgres, Docker and Docker Compose
Francesco Ciulla
Francesco Ciulla

Posted on • Updated on

JavaScript CRUD Rest API using Nodejs, Express, Sequelize, Postgres, Docker and Docker Compose

Let's create a CRUD rest API in JavaScript, using:

  • Node.js
  • Express
  • Sequelize
  • Postgres
  • Docker
  • Docker Compose

All the code is available in the GitHub repository (link in the video description): https://youtube.com/live/Uv-jMWV29rU


Intro

Here is a schema of the architecture of the application we are going to create:

crud, read, update, delete, to a node.js app and postgres service, connected with docker compose. POstman and tableplus to test it

We will create 5 endpoints for basic CRUD operations:

  • Create
  • Read all
  • Read one
  • Update
  • Delete

We will create a Node.js application using:

  • Express as a framework
  • Sequelize as an ORM
  1. We will Dockerize the Node.js application

  2. We will have a Postgres istance, we will test it with Tableplus

  3. We will create a docker compose file to run both the services

  4. We will test the APIs with Postman


Step-by-step guide

Here is a step-by step guide.

create a new folder

mkdir node-crud-api
Enter fullscreen mode Exit fullscreen mode

step into it

cd node-crud-api
Enter fullscreen mode Exit fullscreen mode

initialize a new npm project

npm init -y
Enter fullscreen mode Exit fullscreen mode

install the dependencies

npm i express pg sequelize
Enter fullscreen mode Exit fullscreen mode
  • express is the Node.js framework
  • pg is a driver for a connection with a Postgres db
  • sequelize is the ORM so we avoid typing SQL queries

create 4 folders

mkdir controllers routes util models
Enter fullscreen mode Exit fullscreen mode

Open the folder with your favorite IDE. If you have Visual Studio Code, you can type this from the terminal:

code .
Enter fullscreen mode Exit fullscreen mode

You should now have a folder similar to this one:

controller, routes, model, util folder, a node_modules and package.json file, package-lock.json file

Now let's start coding.

Database connection

Create a file called "database.js" inside the "util" folder.

This file will contain the internal configuration to allow the connection between the Node.js application and the running Postgres instance.

Populate the util/database.js file

const Sequelize = require('sequelize');

const sequelize = new Sequelize(
    process.env.PG_DB,
    process.env.PG_USER,
    process.env.PG_PASSWORD,
    {
        host: process.env.PG_HOST,
        dialect: 'postgres',
    }
);

module.exports = sequelize;
Enter fullscreen mode Exit fullscreen mode

User model

Create a file called "user.js" inside the "models" folder.

This file will contain the model, in this case a user with an auto-incremented id, a name and an email.

Populate the models/user.js file:

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

const User = db.define('user', {
    id: {
        type: Sequelize.INTEGER,
        autoIncrement: true,
        allowNull: false,
        primaryKey: true
    },
    name: Sequelize.STRING,
    email: Sequelize.STRING
});

module.exports = User;
Enter fullscreen mode Exit fullscreen mode

Controllers

This is the file that contains all the functions to execute in order to interact with the database and have the 4 basic functionalities:

Create a file called "users.js" inside the "controllers" folder

Populate the controllers/users.js file

const User = require('../models/user');

// CRUD Controllers

//get all users
exports.getUsers = (req, res, next) => {
    User.findAll()
        .then(users => {
            res.status(200).json({ users: users });
        })
        .catch(err => console.log(err));
}

//get user by id
exports.getUser = (req, res, next) => {
    const userId = req.params.userId;
    User.findByPk(userId)
        .then(user => {
            if (!user) {
                return res.status(404).json({ message: 'User not found!' });
            }
            res.status(200).json({ user: user });
        })
        .catch(err => console.log(err));
}

//create user
exports.createUser = (req, res, next) => {
  const name = req.body.name;
  const email = req.body.email;
  User.create({
    name: name,
    email: email
  })
    .then(result => {
      console.log('Created User');
      res.status(201).json({
        message: 'User created successfully!',
        user: result
      });
    })
    .catch(err => {
      console.log(err);
    }); 
}

//update user
exports.updateUser = (req, res, next) => {
  const userId = req.params.userId;
  const updatedName = req.body.name;
  const updatedEmail = req.body.email;
  User.findByPk(userId)
    .then(user => {
      if (!user) {
        return res.status(404).json({ message: 'User not found!' });
      }
      user.name = updatedName;
      user.email = updatedEmail;
      return user.save();
    })
    .then(result => {
      res.status(200).json({message: 'User updated!', user: result});
    })
    .catch(err => console.log(err));
}

//delete user
exports.deleteUser = (req, res, next) => {
  const userId = req.params.userId;
  User.findByPk(userId)
    .then(user => {
      if (!user) {
        return res.status(404).json({ message: 'User not found!' });
      }
      return User.destroy({
        where: {
          id: userId
        }
      });
    })
    .then(result => {
      res.status(200).json({ message: 'User deleted!' });
    })
    .catch(err => console.log(err));
}
Enter fullscreen mode Exit fullscreen mode

Routes

Create a file called "users.js" inside the "routes" folder.

Populate the routes/users.js file

const controller = require('../controllers/users');
const router = require('express').Router();

// CRUD Routes /users
router.get('/', controller.getUsers); // /users
router.get('/:userId', controller.getUser); // /users/:userId
router.post('/', controller.createUser); // /users
router.put('/:userId', controller.updateUser); // /users/:userId
router.delete('/:userId', controller.deleteUser); // /users/:userId

module.exports = router;
Enter fullscreen mode Exit fullscreen mode

Index file

To run our application we need to create on more file at the root level. this is the file that will be executed by the docker container.

in the root folder, create a file called index.js

Populate the "index.js file":

const express = require('express');
const bodyparser = require('body-parser');
const sequelize = require('./util/database');
const User = require('./models/user');

const app = express();

app.use(bodyparser.json());
app.use(bodyparser.urlencoded({ extended: false }));

app.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  next();
});

//test route
app.get('/', (req, res, next) => {
  res.send('Hello World');
});

//CRUD routes
app.use('/users', require('./routes/users'));

//error handling
app.use((error, req, res, next) => {
  console.log(error);
  const status = error.statusCode || 500;
  const message = error.message;
  res.status(status).json({ message: message });
});

//sync database
sequelize
  .sync()
  .then(result => {
    console.log("Database connected");
    app.listen(3000);
  })
  .catch(err => console.log(err));
Enter fullscreen mode Exit fullscreen mode

Docker Part

Let's create 3 more files at the root level:

  • .dockerignore (it starts with a dot)
  • Dockerfile (capital D)
  • docker-compose.yml

The structure should look like this:

Image description

the .dockerignore will contain a single line:

node_modules
Enter fullscreen mode Exit fullscreen mode

the .dockerignore file with a single line: node_modules


The Dockerfile

To create a Docker image we need a simple yet powerfule file. That's called "Dockerfile" (capital D). We might use a different name but let's keep things simple for now.

FROM node:14

# Create app directory
WORKDIR /app

COPY package*.json ./

RUN npm install

# Bundle app source
COPY . .

EXPOSE 3000

CMD [ "node", "index.js" ]
Enter fullscreen mode Exit fullscreen mode

Docker compose file

To run multiple services an easy way is to create a file called "docker-compose.yml"

The docker-compose.yml file:

version: "3.9"

services:
  node_app:
    container_name: node_app
    build: .
    image: francescoxx/node_live_app
    ports:
      - "3000:3000"
    environment:
      - PG_DB=node_live_db
      - PG_USER=francesco
      - PG_PASSWORD=12345
      - PG_HOST=node_db
    depends_on:
      - node_db

  node_db:
    container_name: node_db
    image: postgres:12
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_DB=node_live_db
      - POSTGRES_USER=francesco
      - POSTGRES_PASSWORD=12345
    volumes:
      - node_db_data:/var/lib/postgresql/data

volumes:
  node_db_data: {}
Enter fullscreen mode Exit fullscreen mode

Build the Docker image and run the docker containers

Run Postgres in a container

First, let's run the postgres container:

docker compose up -d node_db
Enter fullscreen mode Exit fullscreen mode

To check the logs, we can type:

docker compose logs
Enter fullscreen mode Exit fullscreen mode

you should get an output similar to this one:

..... 2023-02-12 13:07:41.342 UTC [1] LOG:  database system is ready to accept connections

if we see "database system is ready to accept connections" we are good to go!

Let's test it using TablePlus.

Click on the + to create a new connection

Image description

copy the values from the docker-compose.yml file. (password is 12345 if you left the values as they are)

Image description

Build and run the Docker service

Second, let's build our Docker iamge:

docker compose build
Enter fullscreen mode Exit fullscreen mode

Image description

finally, let's start the service:

docker compose up node_app
Enter fullscreen mode Exit fullscreen mode

This should be the output on the terminal

Image description

Test the app with Postman

Let's test the app using Postman.

Make a GET request to localhost:3000

Image description

Make a GET request to localhost:3000/users

We should have an empty array as a response

Image description

Let's create 3 users: aaa, bbb, and ccc

Image description

Image description

Image description

Let's check again all the users:

Make a GET request to localhost:3000/users

We should see 3 users:

Image description

Let's get a single user, for example the user 2

Make a GET request to localhost:3000/users/2

Image description

Let's update an existing user, for example the same user 2

Make a PUT reqeust to localhost:3000/users/2 with a different body

Image description

Finally, let's delete the user number 3

Make a DELETE reuqest to localhost:3000/users/3

Image description

We can also check the values using TablePlus

Image description

Conclusion

This is a basic example of how you can build a CRUD rest API using Node.js, Express, Sequelize, Postres, Docker, and Docker Compose.

All the code is available in the GitHub repository (link in the video description): https://youtube.com/live/Uv-jMWV29rU

That's all.
If you have any question, drop a comment below.

Francesco

Oldest comments (73)

Collapse
 
pradumnasaraf profile image
Pradumna Saraf

Great blog

Collapse
 
francescoxx profile image
Francesco Ciulla

Pradumna the best!

Collapse
 
toul_codes profile image
Toul

Nice write up! If I ever use this stack I’ll be sure to keep this article nearby.

Collapse
 
francescoxx profile image
Francesco Ciulla

thank you Toul!

Collapse
 
traleeee profile image
Tra Le

Something missin... hmm, maybe that's typescript i guess

Collapse
 
francescoxx profile image
Francesco Ciulla

ye probably

Collapse
 
bobbyiliev profile image
Bobby Iliev

Great post!

Collapse
 
francescoxx profile image
Francesco Ciulla

thanks Bobby!

Collapse
 
nikofox profile image
sborowski

Great post. It's been long since I last used express and sequelize.
Too bad there's no validation for incoming data.

Collapse
 
francescoxx profile image
Francesco Ciulla

indeed. there are probably other ways to validate input tho and I should double check

Collapse
 
kaybeckmann profile image
KayBeckmann

I am new at coding. Will try it on the weekend. Thank you for this tutorial.

Collapse
 
francescoxx profile image
Francesco Ciulla

please let me know if it worked. thank you

Collapse
 
kaybeckmann profile image
KayBeckmann

Finaly I tested it today.
It worked very well.
a little spelling mistakes in "docker-compose" for starting the second container, but this was easy to fix.
now I can make a little backend for testing in local network :D
Thank you so much.

Thread Thread
 
francescoxx profile image
Francesco Ciulla

amazing, glad it helped. do you mean a spelling mistake on your end or in the tutorial?

Collapse
 
lotfijb profile image
Lotfi Jebali

Thanks for sharing, well explained and documented 😁

Collapse
 
francescoxx profile image
Francesco Ciulla

thank you @Lotfij

Collapse
 
codeofrelevancy profile image
Code of Relevancy

A great resource for anyone looking to build a CRUD API..

Collapse
 
francescoxx profile image
Francesco Ciulla

thank you @codeofrelevancy !!

Collapse
 
clericcoder profile image
Abdulsalaam Noibi

WOW,What a great knowledge you have shared Francesco.

Collapse
 
francescoxx profile image
Francesco Ciulla

thank you @noibisdev

Collapse
 
dlmarroco profile image
Danilo Marroco

Great post, thanks for sharing!

Collapse
 
francescoxx profile image
Francesco Ciulla

Thank you Danilo, highly appreciated!

Collapse
 
alexsmith12 profile image
Alex smith

fantastic post Francesco, what excellent details you have offered.
I'm grateful.

Collapse
 
francescoxx profile image
Francesco Ciulla

you are welcome Alex!

Collapse
 
nerajno profile image
Nerando Johnson

Time to use this for an idea I have....

Collapse
 
francescoxx profile image
Francesco Ciulla

good luck

Collapse
 
cavo789 profile image
Christophe Avonture

A few weeks ago, I've discover postgrest, just crazy easy. postgrest.org/en/stable/index.html

It's also open data, directly query your postgresql db just using URIs.

Collapse
 
charlesr1971 profile image
Charles Robertson

This is a first class tutorial. Very clear and precise.
I’ve been a server side Dev for nearly 20 years, using a language called Coldfusion. In Coldfusion we have a tag called CFQUERY, which we can write SQL, inside.
So, for example:

<cfquery name="getEmployees" datasource="cfdocexamples">
 SELECT FIRSTNAME,LASTNAME,EMAIL,PHONE
 FROM EMPLOYEES
</cfquery>
Enter fullscreen mode Exit fullscreen mode

Obviously, you can write extremely complex SQL queries here.
I have only been using NodeJs for a few years now and have always wondered how easy it is to increase the complexity of the SQL query in a NodeJS Rest API application?

CRUD is all very well and is great for highlighting the basics, but, in the real world, business requirements might warrant more complexity?

Collapse
 
rafaelcerqueira profile image
Rafael Freitas

Amazing! Thank you for the post.

Collapse
 
francescoxx profile image
Francesco Ciulla

you are welcome Rafael