Langsung ke konten utama

TypeScript CRUD Rest API, using: Nest.js, TypeORM, Postgres, Docker and Docker Compose

 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

Postingan populer dari blog ini

You’re Using ChatGPT Wrong! Here’s How to Be Ahead of 99% of ChatGPT Users

 Most of us use ChatGPT wrong. We don’t include examples in our prompts. We ignore that we can control ChatGPT’s behavior with roles. We let ChatGPT guess stuff instead of providing it with some information. This happens because we mostly use standard prompts that might help us get the job done once, but not all the time. We need to learn how to create high-quality prompts to get better results. We need to learn prompt engineering! And, in this guide, we’ll learn 4 techniques used in prompt engineering. If you don’t feel like reading, you can watch my video below. Few Shot Standard Prompts Few shot standard prompts are the standard prompts we’ve seen before, but with examples of the task in them. Why examples? Well, If you want to increase your chances to get the desired result, you have to add examples of the task that the prompt is trying to solve. Few-shot standard prompts consist of a task description, examples, and the prompt. In this case, the prompt is the beginning of a new exa

My kids and I just played D&D with ChatGPT4 as the DM

 My kids and I just played D&D with ChatGPT4 as the DM My two oldest kids Taylor Anne and Liam, are 26 and 23 (respectively). My youngest son Tenzin is 15 years old. All of us are together at my house in Mexico City this week, and the discussion topic generating the most heat is OpenAI and GPT4. Since Tenzin has been playing tabletop RPG games like D&D for years and has a lot of experience as a DM, we discussed playing a campaign together. But then Tenzin had the bright idea of letting ChatGPT be the DM. What follows in this blog post is the transcript of the game that we played. Where we tell ChatGPT that we rolled dice, we are using Google’s die roll mechanism. I feel like the transcript speaks for itself, but before reproducing it I will state for the historical record that my mind is still exploding from from all the inevitable innovation and legal controversies this kind of usage of LLM (Large Language Model) technologies will provoke in the coming months and years. Softwa

March Madness: Relationships and Basketball have a lot in Common

 Co-authored with  Robin Stern, PhD ’Tis the season for March Madness, and we don’t mean just basketball. A lesser-known use of the phrase popularized in the early 1900s is in reference to “a form of madness or uncharacteristic behavior said to affect people in March”. For anyone on the East coast familiar with — and, not a fan of — the inclement shifts in weather and lingering dark days of winter, this makes sense. And there is no shortage of research indicating that simply surviving the bleakest time of year is a feat of mind, body, and spirit. But when this madness bleeds into our relationships, as it inevitably can, it is important to know how you can regulate the burgeoning restlessness of early Spring and cultivate healthier connections. And for those of you who just can’t think about March Madness without basketball — take this opportunity to think about what basketball and relationships have in common: practicing necessary skills and good form to move both forward; remembering