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

Latest comments (73)

Collapse
 
shoban12 profile image
Mude Shoban Babu

Thanks so much for detailed course. Just a correction in link,

The youtube link that you have kept in the bottom of the course content takes to "Build a CRUD Rest API with Kotlin, Postgres, Docker and docker compose" instead of "JavaScript CRUD Rest API using Nodejs, Express, Sequelize, Postgres, Docker and Docker Compose", it should be updated to https://www.youtube.com/watch?v=Uv-jMWV29rU&t=236s

Collapse
 
francescoxx profile image
Francesco Ciulla

fixed, thanks!

Collapse
 
shoban12 profile image
Mude Shoban Babu

Thank you Francesco.

Thread Thread
 
francescoxx profile image
Francesco Ciulla

you are welcome Mude

Collapse
 
charlesr1971 profile image
Charles Robertson

Hi Francesco

I have set up a codesandbox VM, to try and emulate this tutorial.

I see that your table is called:

Users
Enter fullscreen mode Exit fullscreen mode

But you connect via:

const User = define("user", {
  id: {
    type: INTEGER,
    autoIncrement: true,
    allowNull: false,
    primaryKey: true,
  },
  name: STRING,
  email: STRING,
});
Enter fullscreen mode Exit fullscreen mode

How does your code find the Users table?

Collapse
 
ebuka1anthony profile image
ebuka anthony

nice article, i learnt a lot, u keep inspiring me fran

Collapse
 
francescoxx profile image
Francesco Ciulla

thank you Ebuka!

Collapse
 
ebuka1anthony profile image
ebuka anthony

you're welcome, now let me use your tutorial article & practicccccccccccccce

Thread Thread
 
francescoxx profile image
Francesco Ciulla

good luccccccck

Collapse
 
spaniard95 profile image
Panagiotis Spanos

Hello Francesco,
i really enjoyed the tutorial but i am stuck in the tableplus step. When i connect with the node_live_db its not empty

Image description
Any ideas why that happened?
Image description

Collapse
 
francescoxx profile image
Francesco Ciulla

interesting, can you try to remove the volume and try again? of course be careful if you are ona production environment!!

Collapse
 
spaniard95 profile image
Panagiotis Spanos • Edited

Still the same, i first stopped the container and deleted the volume and recreated it and second try i stopped the container and i changed the docker-compose.yml file volumes part with
volumes:
# node_db_data: {} # remove the old volume
data: {} # create a new volume

to maybe create a new db folder, but both tries it was not empty.

I also installed psql to check the db and i get that "i did not find any relations" when i typed \dt

Thread Thread
 
francescoxx profile image
Francesco Ciulla

try to copy past my docker compose and try agaqin

Collapse
 
sikehish_ profile image
Hisham Akmal

Well written and documented. Thanks a ton! But i've got one doubt regarding docker compose and docker build. Arent we supposed to build the image first and name it node_live_app using the docker build command and then execute docker compose build?

Collapse
 
francescoxx profile image
Francesco Ciulla

no, docker compose build just builds the image. you can even just run docker compose up --build , or docker compose up.

I just did it to show the step-by-step commands to make it clearer

Collapse
 
sikehish_ profile image
Hisham Akmal

Oh okay. Thank you!

Thread Thread
 
francescoxx profile image
Francesco Ciulla

you are welcome

Collapse
 
edwardfernando profile image
Edward Fernando

Thanks for this tutorial @francescoxx

When I run this on my machine, although the server is started, it is stuck when calling the endpoint. It does not returning the response.

Im currently using mac with m1 chip. Any pointers about this?

Collapse
 
francescoxx profile image
Francesco Ciulla

try docker ps -a. can you see the containers up and running?

Collapse
 
edwardfernando profile image
Edward Fernando • Edited

Oh, this is because i missed the next() function call.

app.use((req, res, next) => {
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
    next();
});
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
francescoxx profile image
Francesco Ciulla

oh I see! glad you fixed it

Collapse
 
edwardfernando profile image
Edward Fernando
➜  ~ docker ps -a
CONTAINER ID   IMAGE               COMMAND                  CREATED        STATUS          PORTS                              NAMES
d3f626718969   efs/node_live_app   "docker-entrypoint.s…"   15 hours ago   Up 39 seconds   3000/tcp, 0.0.0.0:3333->3333/tcp   node_app
3c423213aeff   postgres:12         "docker-entrypoint.s…"   15 hours ago   Up 15 hours     0.0.0.0:5432->5432/tcp             node_db
Enter fullscreen mode Exit fullscreen mode

It is actually running

Thread Thread
 
edwardfernando profile image
Edward Fernando

in the index.js, i tried to commented out these lines and it is working now:

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

I still don't understand what happened. Any pointers / explanation would be appreciated much.

Collapse
 
farhanacsebd profile image
Farhana Binte Hasan

Will use this one day. Thanks for sharing and making a detailed article.

Collapse
 
francescoxx profile image
Francesco Ciulla

you are welcome

Collapse
 
antonfridlund profile image
Anton Fridlund

Hello!
I think this read was quite interesting, good guide.

There were however a few things i noticed that I don't often see in modern JavaScript programming.

Usually you would avoid chaining calls with ".then" since it is bad practice, use "await" instead. Is there a reason why you chain your calls like this even when it's bad practice?

Exporting arrow functions in the controller leads to poor readability and is also not a common way to handle controllers. You would probably want to create a class that represents the controller and export that class. Do you have a reason for exporting arrow functions?

Something that I was wondering throughout the guide was why you use "require" instead of "import", basically why commonjs instead of esm? Commonjs is since long dying and i would not recommend using it.

The JavaScript code in the guide seems to be quite old and I would be careful if I were to use it.

Collapse
 
francescoxx profile image
Francesco Ciulla

nice breakdown Anton! this is not optimized and it's meant to get things fast. the focus here was more on creating the endpoints and the docker image and the connection with the db.

about the async await, I just used the suggested code, but in other guides I used the await, honestly I prefer that one

this is not meant to be a production ready code, but it's a way to undesrtand the basics!

Thanks for your feedback, I will probably level up the code in the next guide!

Collapse
 
antonfridlund profile image
Anton Fridlund

Thank you for the explanation!
I was happy to see such a thorough guide as a whole.

It was interesting to see how you containerize the application and I also liked the testing of the end product using Postman and TablePlus, great tools.

Thank you for the guide!

Thread Thread
 
francescoxx profile image
Francesco Ciulla

my idea was to start with this and maybe focus more on the upcoming Typescript example. this article is a breakthrough of something I coded live on youtube.

Thank yuo for your feedback, highly appreciated instead of the usual "asweome" comment! 💙

Thread Thread
 
karlkras profile image
Karl Krasnowsky

A typescript version would be great! Thanks for your effort.
Awesome! 😄

Thread Thread
 
francescoxx profile image
Francesco Ciulla

it's on the todo list. thanks

Collapse
 
lbino97 profile image
lbino97

Amei o post

Collapse
 
francescoxx profile image
Francesco Ciulla

🔥

Collapse
 
yandev profile image
Yan

Thanks a lot !

Collapse
 
francescoxx profile image
Francesco Ciulla

you are welcome

Collapse
 
jakeroid profile image
Ivan Karabadzhak

Nice post 👍

Collapse
 
francescoxx profile image
Francesco Ciulla

you are welcome

Collapse
 
fu91t1v3 profile image
⚡Abiola⚡

This is beautiful. Never tried Postgres with Node and Express, before, but this is surely gonna help. Thanks.

Collapse
 
francescoxx profile image
Francesco Ciulla

you are welcome!

Collapse
 
ashishbeetle profile image
Ashish Binu

Awesome ! <3

Collapse
 
francescoxx profile image
Francesco Ciulla

thank you

Collapse
 
odeyale2016 profile image
Odeyale Kehinde Musiliudeen

Welldone Boss

Collapse
 
francescoxx profile image
Francesco Ciulla

you are welcome

Collapse
 
rafaelcerqueira profile image
Rafael Freitas

Amazing! Thank you for the post.

Collapse
 
francescoxx profile image
Francesco Ciulla

you are welcome Rafael