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:
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
We will Dockerize the Node.js application
We will have a Postgres istance, we will test it with Tableplus
We will create a docker compose file to run both the services
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
step into it
cd node-crud-api
initialize a new npm project
npm init -y
install the dependencies
npm i express pg sequelize
- 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
Open the folder with your favorite IDE. If you have Visual Studio Code, you can type this from the terminal:
code .
You should now have a folder similar to this one:
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;
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;
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));
}
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;
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));
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:
the .dockerignore will contain 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" ]
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: {}
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
To check the logs, we can type:
docker compose logs
you should get an output similar to this one:
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
copy the values from the docker-compose.yml file. (password is 12345 if you left the values as they are)
Build and run the Docker service
Second, let's build our Docker iamge:
docker compose build
finally, let's start the service:
docker compose up node_app
This should be the output on the terminal
Test the app with Postman
Let's test the app using Postman.
Make a GET request to localhost:3000
Make a GET request to localhost:3000/users
We should have an empty array as a response
Let's create 3 users: aaa, bbb, and ccc
Let's check again all the users:
Make a GET request to localhost:3000/users
We should see 3 users:
Let's get a single user, for example the user 2
Make a GET request to localhost:3000/users/2
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
Finally, let's delete the user number 3
Make a DELETE reuqest to localhost:3000/users/3
We can also check the values using TablePlus
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.
Oldest comments (73)
Great blog
Pradumna the best!
Nice write up! If I ever use this stack I’ll be sure to keep this article nearby.
thank you Toul!
Something missin... hmm, maybe that's typescript i guess
ye probably
Great post!
thanks Bobby!
Great post. It's been long since I last used express and sequelize.
Too bad there's no validation for incoming data.
indeed. there are probably other ways to validate input tho and I should double check
I am new at coding. Will try it on the weekend. Thank you for this tutorial.
please let me know if it worked. thank you
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.
amazing, glad it helped. do you mean a spelling mistake on your end or in the tutorial?
Thanks for sharing, well explained and documented 😁
thank you @Lotfij
A great resource for anyone looking to build a CRUD API..
thank you @codeofrelevancy !!
WOW,What a great knowledge you have shared Francesco.
thank you @noibisdev
Great post, thanks for sharing!
Thank you Danilo, highly appreciated!
fantastic post Francesco, what excellent details you have offered.
I'm grateful.
you are welcome Alex!
Time to use this for an idea I have....
good luck
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.
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:
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?
Amazing! Thank you for the post.
you are welcome Rafael