Let's create a CRUD Rest API in Typescript using:
NestJS (NodeJS framework)
TypeORM (ORM: Object Relational Mapper)
Postgres (relational database)
Docker (for containerization)
Docker Compose
If you prefer a video version:
All the code is available in the GitHub repository (link in the video description): youtube.com/live/gqFauCpPSlw
Here is a schema of the architecture of the application we are going to create:
crud, read, update, delete, to a NestJS 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
Here are the steps we are going through:
Create a new NestJS application
Create a new module for the users, with a controller, a service and an entity
Dockerize the application
Create docker-compose.yml to run the application and the database
Test the application with Postman and Tableplus
We will go with a step-by-step guide, so you can follow along.
Requirements:
Node installed (I'm using v16)
Docker installed and running
(Optional): Postman and Tableplus to follow along, but any testing tool will work
NestJS CLI (command below)
💻 Create a new NestJS application
We will create our project using the NestJS CLI
if you don't have the NestJS CLI installed, you can install it with:
COPY
COPY
COPY
COPY
npm install -g @nestjs/cli
This will install the NestJS CLI globally, so you can use it from anywhere.
Then you can create move to your workspace folder and create a new NestJS application with (you can replace nest-crud-app with what you want):
COPY
COPY
COPY
COPY
nest new nest-crud-app
Just hit enter to go with the default options. This will create a new project for you (it will take a while).
successfully created project nest-crud-app
Step into the directory:
COPY
COPY
COPY
COPY
cd nest-crud-app
Now install the dependencies we need:
COPY
COPY
COPY
COPY
npm i pg typeorm @nestjs/typeorm @nestjs/config
pg: Postgres driver for NodeJS
typeorm: ORM for NodeJS
@nestjs/typeorm: NestJS module for TypeORM
@nestjs/config: NestJS module for configuration
Once it's done, open the project in your favorite editor (I'm using VSCode).
COPY
COPY
COPY
COPY
code .
Before we start coding, let's test if everything is working.
COPY
COPY
COPY
COPY
npm start
And we should see something like that:
Hello World on the left, vs code witn npm start command run, NestJS scaffold project
Now you can stop the server with Ctrl + C.
🐈⬛ Create the NestJS application
Now we are going to work on the NestJS application.
Let's create a new module, a controller, a service and an entity.
COPY
COPY
COPY
COPY
nest g module users
nest g controller users
nest g service users
touch src/users/user.entity.ts
This will create the following files (and 2 more test files we will not use)
src/users/users.module.ts
src/users/users.controller.ts
src/users/users.service.ts
src/users/user.entity.ts
Your folder structure should look like that:
folder structure of the NestJS app, the 4 files are ina folder called users in src
Now let's work on these 4 files.
User Entity
Open the file "src/users/user.entity.ts" and populate it like that:
COPY
COPY
COPY
COPY
import { Entity, PrimaryGeneratedColumn, Column, } from "typeorm";
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
email: string;
}
Explanation:
We are using the decorator @Entity() to tell TypeORM that this is an entity
We are using the decorator @PrimaryGeneratedColumn() to tell TypeORM that this is the primary key of the table
We are using the decorator @Column() to tell TypeORM that this is a column of the table
We are creating a User entity with 3 columns: id, name and email.
User Service
Open the file "src/users/users.service.ts" and populate it like that:
COPY
COPY
COPY
COPY
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import {User} from './user.entity';
@Injectable()
export class UserService {
constructor(
@InjectRepository(User)
private userRepository: Repository<User>,
) {}
async findAll(): Promise<User[]> {
return this.userRepository.find();
}
async findOne(id: number): Promise<User> {
return this.userRepository.findOne({ where: { id } });
}
async create(user: Partial<User>): Promise<User> {
const newuser = this.userRepository.create(user);
return this.userRepository.save(newuser);
}
async update(id: number, user: Partial<User>): Promise<User> {
await this.userRepository.update(id, user);
return this.userRepository.findOne({ where: { id } });
}
async delete(id: number): Promise<void> {
await this.userRepository.delete(id);
}
}
Explanation:
We are using the decorator @Injectable() to tell NestJS that this is a service
We are using the decorator @InjectRepository(User) to tell NestJS that we want to inject the repository of the User entity
We are using the decorator @Repository(User) to tell NestJS that we want to inject the repository of the User entity
We are creating a UserService with 5 methods: findAll, findOne, create, update and delete
User Controller
Open the file "src/users/users.controller.ts" and populate it like that:
COPY
COPY
COPY
COPY
import { Controller, Get, Post, Body, Put, Param, Delete, NotFoundException } from '@nestjs/common';
import { UsersService } from './users.service';
import { User } from './user.entity';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
//get all users
@Get()
async findAll(): Promise<User[]> {
return this.usersService.findAll();
}
//get user by id
@Get(':id')
async findOne(@Param('id') id: number): Promise<User> {
const user = await this.usersService.findOne(id);
if (!user) {
throw new NotFoundException('User does not exist!');
} else {
return user;
}
}
//create user
@Post()
async create(@Body() user: User): Promise<User> {
return this.usersService.create(user);
}
//update user
@Put(':id')
async update (@Param('id') id: number, @Body() user: User): Promise<any> {
return this.usersService.update(id, user);
}
//delete user
@Delete(':id')
async delete(@Param('id') id: number): Promise<any> {
//handle error if user does not exist
const user = await this.usersService.findOne(id);
if (!user) {
throw new NotFoundException('User does not exist!');
}
return this.usersService.delete(id);
}
}
Explanation:
We are using the decorator @Controller('users') to tell NestJS that this is a controller, and that the route is "users"
We are defining the constructor of the class, and injecting the UserService
We are defining 5 methods: findAll, findOne, create, update and delete, decorated with the HTTP method we want to use, and we are using the UserService to call the corresponding method
User Module
Open the file "src/users/users.module.ts" and populate it like that:
COPY
COPY
COPY
COPY
import { Module } from '@nestjs/common';
import { UserController } from './users.controller';
import { UserService } from './users.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';
@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UserController],
providers: [UserService]
})
export class UsersModule {}
Explanation:
We are importing the TypeOrmModule and the User entity (UserController and UserService are already imported)
We are using the decorator @Module() to tell NestJS that this is a module
We add the TypeOrmModule.forFeature([User]) to the imports array, to tell NestJS that we want to use the User entity
Update the Main Module
Open the file "src/app.module.ts" and populate it like that:
COPY
COPY
COPY
COPY
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UsersModule } from './users/users.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot(),
UsersModule,
TypeOrmModule.forRoot({
type: process.env.DB_TYPE as any,
host: process.env.PG_HOST,
port: parseInt(process.env.PG_PORT),
username: process.env.PG_USER,
password: process.env.PG_PASSWORD,
database: process.env.PG_DB,
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true,
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Explanation:
We are importing the ConfigModule, the UsersModule and the TypeOrmModule
We are importing the ConfigModule, UsersModule and TypeOrmModule in the imports array
For TypeOrmModule, we are using the method forRoot() to tell NestJS that we want to use the default connection, and we define some environment variables to connect to the database. We will set the in the docker-compose.yml file soon.
the synchronize option is set to true, so that the database schema is automatically updated when the application is started
🐳 Dockerize the application
Let's create 3 files to dockerize the application: a Dockerfile and a .dockerignore file.
COPY
COPY
COPY
COPY
touch Dockerfile .dockerignore docker-compose.yml
.dockerignore
A .dockerignore file is used to tell Docker which files and directories to ignore when building the image.
If you are familiar with the .gitignore file, it works the same way.
Open the file ".dockerignore" and populate it like that:
COPY
COPY
COPY
COPY
node_modules
dist
.git
This will tell Docker to ignore the node_modules, dist and .git directories when building the image.
Dockerfile
A Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image.
Open the file "Dockerfile" and populate it like that:
COPY
COPY
COPY
COPY
FROM node:16
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["npm", "run", "start:prod"]
Explanation:
FROM node:16 is used to tell Docker which image to use as a base image.
WORKDIR is the directory where the commands will be executed. In our case, it's the /app directory.
COPY package*.json is used to copy the package.json and package-lock.json files to the /app directory.
RUN npm install is used to install the dependencies.
COPY . . is used to copy all the files from the current directory to the /app directory.
RUN npm run build is used to build the application.
EXPOSE is used to expose the port 3000 to the host.
CMD is used to execute a command when the container is started, in our case, it's "npm run start:prod".
docker-compose.yml file
We will use docker compose to run the application and the database.
Populate the file "docker-compose.yml" like that:
COPY
COPY
COPY
https://dragonsofficeboxhdq.statuspage.io/
https://workes-the-movie-office-box.statuspage.io/
https://workesthemovieofficeboxhdq.statuspage.io/
https://dragons-honor-among-office-box.statuspage.io/
https://themariomovieofficeboxhdq.statuspage.io/
https://lonely-castle-in-the-mirror-korean-hdq.statuspage.io/
https://lonely-castle-in-the-mirror-korea-film.statuspage.io/
https://lonelycastleinthemirror2023.statuspage.io/
https://lonelycastleinthemirrorboxkorea.statuspage.io/
https://lonelycastleinthemirrormovies.statuspage.io/
https://lonelycastleinthemirrorboxko.statuspage.io/
https://lonely-castle-in-the-mirror-movie-full.statuspage.io/
https://lonely-castle-in-the-mirror-korean-full.statuspage.io/
https://lonelycastleinthemirrorhd4k.statuspage.io/
https://lonely-castle-in-the-mirror-korean.statuspage.io/
https://renfieldfilmfreewatchonline.statuspage.io/
https://renfield-watch-online-hd-hongkong.statuspage.io/
https://hk-renfield-watch-online-hd-tw.statuspage.io/
https://renfieldfilmfreewatchonlinetw.statuspage.io/
https://renfield-hd-online-full-tw.statuspage.io/
https://renfieldmoviesfoxs.statuspage.io/
https://renfieldfilmmoviesfull.statuspage.io/
https://renfield-movies-foxs-tw.statuspage.io/
https://renfieldfilmfreewatchonlinefree.statuspage.io/
https://renfield-watch-online-hd-taiwan.statuspage.io/
https://rideonmoviesfoxs.statuspage.io/
https://rideonmoviesfoxstaiwan.statuspage.io/
https://ride-on-movies-online-hd.statuspage.io/
https://ride-on-movies-online-hd-full.statuspage.io/
https://rideonmoviesfoxstaiwanfree.statuspage.io/
COPY
version: '3.9'
services:
nestapp:
container_name: nestapp
image: francescoxx/nestapp:1.0.0
build: .
ports:
- '3000:3000'
environment:
- DB_TYPE=postgres
- PG_USER=postgres
- PG_PASSWORD=postgres
- PG_DB=postgres
- PG_PORT=5432
- PG_HOST=db
depends_on:
- db
db:
container_name: db
image: postgres:12
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
ports:
- '5432:5432'
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata: {}
Explanation:
We are using the version 3.9 of the docker-compose.yml file format
We are defining 2 services: nestapp and db
The nestapp service is used to run the NestJS application
The db service is used to run the Postgres database
The nestapp service depends on the db service, so that the db service is started before the nestapp service
For the nestapp service:
container_name is used to set the name of the container
image is used to set the image to use, in our case, it's francescoxx/nestapp:1.0.0 change francescoxxx with your docker hub username
build is used to build the image from the Dockerfile. we are using the current directory as the build context.
ports is used to expose the port 3000 to the host
environment is used to set the environment variables: DB_TYPE, PG_USER, PG_PASSWORD, PG_DB, PG_PORT, PG_HOST. these variables will be used by the application to connect to the database
depends_on is used to tell docker-compose that the db service must be started before the nestapp service.
For the db service:
container_name is used to set the name of the container
image is used to set the image to use, in our case, it's postgres:12
environment is used to set the environment variables: POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB
ports is used to expose the port 5432 to the host
volumes is used to mount a volume to the container. In our case, we are mounting the pgdata volume to the /var/lib/postgresql/data directory.
We also define the pgdata volume at the end of the file.
Run the Postgres service
To run the Postgres service, we will use the docker-compose command.
COPY
COPY
COPY
COPY
docker compose up -d db
This will run the db service in detached mode.
To check if the service is running, we can use the docker ps command:
COPY
COPY
COPY
COPY
docker ps -a
We should see something like that:
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e045f74a36ca postgres:12 "docker-entrypoint.s…" 23 seconds ago Up 22 seconds 0.0.0.0:5432->5432/tcp db
But let's check it with TablePlus. Open the TablePlus application and connect to the database, by creating a new "Postgres" connection.
You can use the UI and set:
Host: localhost
Port: 5432
Username: postgres
Password: postgres
Database: postgres
Then hit the "Connect" button at the bottom-right.
TablePlus application
Now we are ready to build the Nest app image and run the application.
Build the Nest app image
To build the Nest app image, we will use the docker compose command.
COPY
COPY
COPY
COPY
docker compose build
This will build the image from the Dockerfile.
To check if the image is built, we can use the docker images command:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
francescoxx/nestapp 1.0.0 53267c897590 About an hour ago 1.16GB
postgres 12 1db9fa309607 44 hours ago 373MB
Run the Nest app service
To run the Nest app service, we will use the docker-compose command.
COPY
COPY
COPY
COPY
docker compose up
Test the application
To Test the application, we can use the Postman or any other API client.
First of all let's test if the app is running. Open Postman and create a new GET request.
Postman Get request to localhost:3000
Get all users
To get all users, we can make a GET request to localhost:3000/users.
If we see an empty array it means that its working.
Postman Get request to localhost:3000/users
Create a user
To create a user, we can make a POST request to localhost:3000/users.
In the body, we can use the raw JSON format and set the following data:
COPY
COPY
COPY
COPY
{
"name": "aaa",
"email": "aaa@mail"
}
Postman Post request to localhost:3000/users
You can create 2 more users with the following data:
COPY
COPY
COPY
COPY
{
"name": "bbb",
"email": "bbb@mail"
}
COPY
COPY
COPY
COPY
{
"name": "ccc",
"email": "ccc@mail"
}
Get all the three users
To get all the three users, we can make a GET request to localhost:3000/users.
Postman Get request to localhost:3000/users
Get a user by id
To get a single user, we can make a GET request to localhost:3000/users/2.
Postman Get request to localhost:3000/users/2
Update a user
To update a user, we can make a PUT request to localhost:3000/users/2.
Let's change the name from "bbb" to "Francesco" and the email from "bbb@mail" to "francesco@mail".
COPY
COPY
COPY
COPY
{
"name":"Francesco",
"email":"francesco@mail"
}
Postman PUT request to localhost:3000/users/2
Delete a user
Finally, to delete a user, we can make a DELETE request to localhost:3000/users/3.
Postman DELETE request to localhost:3000/users/3
The answer comes directly from the database.
Final test with TablePlus
Let's check if the data is correctly stored in the database.
As a final test, let's return to TablePlus and check if the data has been updated.
TablePlus application
🏁 Conclusion
We made it! We have built a CRUD rest API in TypeScript, using:
NestJS (NodeJS framework)
TypeORM (ORM: Object Relational Mapper)
Postgres (relational database)
Docker (for containerization)
Docker Compose
If you prefer a video version:
Youtube thumbnail of TypeScript CRUD Rest API, using: Nest.js, TypeORM, Postgres, Docker and Docker Compose
All the code is available in the GitHub repository (link in the video description): youtube.com/live/gqFauCpPSlw
That's all.
If you have any question, drop a comment below.
Francesco
Komentar
Posting Komentar