When Node.js applications grow beyond simple scripts or small APIs, structure becomes just as important as functionality. This is where NestJS shines. Built on top of Express (or Fastify), NestJS brings strong architectural patterns, dependency injection, and TypeScript-first design to the Node ecosystem.

When paired with TypeORM, you get a powerful combination for building clean, maintainable, database-driven REST APIs—without reinventing the wheel.

In this post, we’ll walk through building a REST API using NestJS and TypeORM, covering project setup, entities, repositories, services, controllers, and best practices you’ll want to apply in real-world applications.


What We’re Building

We’ll build a simple REST API for managing users, with full CRUD functionality:

Our stack:


Why NestJS + TypeORM?

Before diving into code, let’s talk about why this combination works so well.

NestJS

NestJS provides:

It feels familiar if you’ve worked with Angular, Spring, or ASP.NET Core.

TypeORM

TypeORM gives you:

Together, they allow you to build APIs that scale cleanly as your project grows.


Step 1: Create a New NestJS Project

First, install the NestJS CLI:

npm install -g @nestjs/cli

Create a new project:

nest new nestjs-typeorm-api

Choose:

Navigate into the project:

cd nestjs-typeorm-api

Step 2: Install TypeORM and Database Dependencies

Install TypeORM and PostgreSQL support:

npm install @nestjs/typeorm typeorm pg

Step 3: Configure TypeORM

Open app.module.ts and configure the database connection.

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: 'localhost',
      port: 5432,
      username: 'postgres',
      password: 'postgres',
      database: 'nestjs_api',
      autoLoadEntities: true,
      synchronize: true,
    }),
  ],
})
export class AppModule {}

Important notes


Step 4: Generate a Resource

NestJS can scaffold most of what we need.

nest generate resource users

Choose:

This creates:

A huge productivity win.


Step 5: Create the User Entity

Inside users/entities/user.entity.ts:

import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column({ unique: true })
  email: string;

  @Column({ default: true })
  isActive: boolean;
}

Why entities matter

Entities:


Step 6: Register the Entity with TypeORM

In users.module.ts:

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { User } from './entities/user.entity';

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  controllers: [UsersController],
  providers: [UsersService],
})
export class UsersModule {}

This allows the User repository to be injected into services.


Step 7: Inject the Repository into the Service

Update users.service.ts:

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './entities/user.entity';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
  ) {}

  create(data: Partial<User>) {
    const user = this.userRepository.create(data);
    return this.userRepository.save(user);
  }

  findAll() {
    return this.userRepository.find();
  }

  findOne(id: number) {
    return this.userRepository.findOneBy({ id });
  }

  update(id: number, data: Partial<User>) {
    return this.userRepository.update(id, data);
  }

  remove(id: number) {
    return this.userRepository.delete(id);
  }
}

Why the repository pattern works


Step 8: Define DTOs for Input Validation

DTOs (Data Transfer Objects) protect your API from bad input.

create-user.dto.ts

export class CreateUserDto {
  name: string;
  email: string;
}

update-user.dto.ts

import { PartialType } from '@nestjs/mapped-types';
import { CreateUserDto } from './create-user.dto';

export class UpdateUserDto extends PartialType(CreateUserDto) {}

Later, you can add validation decorators like @IsEmail() or @IsNotEmpty().


Step 9: Implement the Controller

Update users.controller.ts:

import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Post()
  create(@Body() createUserDto: CreateUserDto) {
    return this.usersService.create(createUserDto);
  }

  @Get()
  findAll() {
    return this.usersService.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.usersService.findOne(+id);
  }

  @Patch(':id')
  update(
    @Param('id') id: string,
    @Body() updateUserDto: UpdateUserDto,
  ) {
    return this.usersService.update(+id, updateUserDto);
  }

  @Delete(':id')
  remove(@Param('id') id: string) {
    return this.usersService.remove(+id);
  }
}

This controller is thin, readable, and focused purely on HTTP concerns.


Step 10: Run and Test the API

Start the server:

npm run start:dev

Test endpoints:

Create a user

POST /users
{
  "name": "James",
  "email": "james@example.com"
}

Get all users

GET /users

Update a user

PATCH /users/1
{
  "name": "Updated Name"
}

Delete a user

DELETE /users/1

Common Mistakes to Avoid

❌ Using synchronize: true in production

Always use migrations in production.


❌ Putting business logic in controllers

Controllers should delegate to services.


❌ Skipping DTOs

DTOs protect your API from accidental data leaks.


❌ Not handling missing records

Throw proper HTTP exceptions (NotFoundException).


Why This Architecture Scales

NestJS with TypeORM scales well because:

This architecture works for:


Next Steps

Once your REST API is working, you can extend it with:


Final Thoughts

Building a REST API with NestJS and TypeORM gives you structure without sacrificing flexibility. You get the discipline of enterprise frameworks with the agility of Node.js — a combination that works extremely well for modern backend systems.

If you’re tired of unstructured Express apps or want a backend framework that grows with your team, NestJS with TypeORM is one of the best choices available today.

If you’d like follow-up posts, I can cover:

Just tell me what you’d like next.

Leave a Reply

Your email address will not be published. Required fields are marked *