DEV Community

Alex Spinov
Alex Spinov

Posted on

NestJS Has a Free Backend Framework: Enterprise-Grade Node.js With Dependency Injection, Modules, and Decorators

Express gives you freedom — and lets you build a 10,000-line server.js. Your routes, middleware, validation, and business logic become an entangled mess. Every new developer asks "where does this go?"

What if Node.js had a framework like Spring Boot or ASP.NET — structured, modular, with dependency injection and clear conventions?

That's NestJS.

Quick Start

npm i -g @nestjs/cli
nest new my-api
cd my-api && npm run start:dev
Enter fullscreen mode Exit fullscreen mode

Controllers — Handle HTTP Requests

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

  @Get()
  findAll(@Query("page") page: number = 1) {
    return this.usersService.findAll(page);
  }

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

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

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

  @Delete(":id")
  @HttpCode(204)
  remove(@Param("id") id: string) {
    return this.usersService.remove(id);
  }
}
Enter fullscreen mode Exit fullscreen mode

Services — Business Logic

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

  async create(dto: CreateUserDto): Promise<User> {
    const user = this.usersRepo.create(dto);
    const saved = await this.usersRepo.save(user);
    await this.emailService.sendWelcome(saved.email);
    return saved;
  }

  async findAll(page: number): Promise<User[]> {
    return this.usersRepo.find({
      skip: (page - 1) * 20,
      take: 20,
      order: { createdAt: "DESC" },
    });
  }

  async findOne(id: string): Promise<User> {
    const user = await this.usersRepo.findOneBy({ id });
    if (!user) throw new NotFoundException("User not found");
    return user;
  }
}
Enter fullscreen mode Exit fullscreen mode

Modules — Organize Your App

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

@Module({
  imports: [
    ConfigModule.forRoot(),
    TypeOrmModule.forRoot({
      type: "postgres",
      url: process.env.DATABASE_URL,
      autoLoadEntities: true,
    }),
    UsersModule,
    AuthModule,
    OrdersModule,
  ],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

Validation With DTOs

import { IsEmail, IsString, MinLength, IsOptional } from "class-validator";

export class CreateUserDto {
  @IsString()
  @MinLength(1)
  name: string;

  @IsEmail()
  email: string;

  @IsString()
  @MinLength(8)
  password: string;
}

export class UpdateUserDto {
  @IsOptional()
  @IsString()
  name?: string;

  @IsOptional()
  @IsEmail()
  email?: string;
}
Enter fullscreen mode Exit fullscreen mode

Validation happens automatically via the ValidationPipe — invalid requests get 400 errors with field-level messages.

Guards — Authentication

@Injectable()
export class JwtAuthGuard implements CanActivate {
  constructor(private jwtService: JwtService) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest();
    const token = request.headers.authorization?.split(" ")[1];

    if (!token) throw new UnauthorizedException();

    try {
      request.user = await this.jwtService.verifyAsync(token);
      return true;
    } catch {
      throw new UnauthorizedException();
    }
  }
}

// Use on routes
@UseGuards(JwtAuthGuard)
@Get("profile")
getProfile(@Request() req) {
  return req.user;
}
Enter fullscreen mode Exit fullscreen mode

Built-in Features

  • Swagger: @nestjs/swagger — auto-generated API docs from decorators
  • WebSockets: @nestjs/websockets — gateway-based real-time communication
  • GraphQL: @nestjs/graphql — code-first or schema-first
  • Microservices: @nestjs/microservices — Redis, RabbitMQ, Kafka, gRPC
  • Task scheduling: @nestjs/schedule — cron jobs
  • Caching: @nestjs/cache-manager — Redis, in-memory

When to Choose NestJS

Choose NestJS when:

  • Building enterprise APIs that need clear structure
  • Your team comes from Java/C#/Spring Boot background
  • You need microservices, WebSockets, or GraphQL built in
  • Long-term maintainability matters more than initial velocity

Skip NestJS when:

  • Simple API with 5-10 routes (Express/Hono is faster to start)
  • You dislike decorators and dependency injection patterns
  • Performance-critical hot path (Fastify raw is faster)
  • Serverless functions (NestJS has overhead for cold starts)

The Bottom Line

NestJS brings enterprise architecture patterns to Node.js without the enterprise pain. Dependency injection, modules, and decorators create structure that scales from 5 routes to 500.

Start here: docs.nestjs.com


Need custom data extraction, scraping, or automation? I build tools that collect and process data at scale — 78 actors on Apify Store and 265+ open-source repos. Email me: Spinov001@gmail.com | My Apify Actors

Top comments (0)