Compartir en Twitter
Go to Homepage

GUÍA PARA CONSTRUIR APIS CON NESTJS PARA PRINCIPIANTES

November 4, 2025

Introducción a NestJS y APIs RESTful

NestJS es un marco MVC para construir aplicaciones del lado del servidor con Node.js, diseñado para ser eficiente y escalable. Utiliza TypeScript de forma nativa, pero permite programar en JavaScript puro, combinando paradigmas como programación orientada a objetos, programación funcional y programación reactiva funcional. Su arquitectura predefinida facilita la creación de aplicaciones testables, escalables y mantenibles, ideal para desarrolladores que buscan estructura en proyectos backend. Este tutorial guía paso a paso en la construcción de una API RESTful de un blog con NestJS, integrando Sequelize para la base de datos Postgres, autenticación con Passport, validación de entradas y protección de rutas con JWT.

Para seguir este tutorial, es necesario tener conocimientos básicos de TypeScript y JavaScript. Experiencia con Angular es útil, pero no imprescindible, ya que se explicarán todos los conceptos necesarios. Se requiere tener instalado Node.js (versión >= 16 recomendada para 2025), Postman para probar endpoints y una base de datos Postgres configurada.

Instalación y configuración inicial

Para comenzar, instala la CLI de NestJS globalmente para facilitar la creación de proyectos. Ejecuta el siguiente comando en tu terminal:

npm i -g @nestjs/cli

Crea un nuevo proyecto y accede al directorio generado:

nest new nest-blog-api
cd nest-blog-api
npm run start:dev

Abre tu navegador en http://localhost:3000 y verifica que aparece el mensaje “Hello World”. Esto confirma que la aplicación NestJS está funcionando correctamente. La estructura inicial del proyecto será:

nest-blog-api/
├── src/
│   ├── app.module.ts
│   ├── main.ts
├── package.json
├── tsconfig.json

Si encuentras errores al ejecutar npm run start:dev, verifica que la versión de TypeScript en package.json sea compatible (por ejemplo, typescript: ^5.2.2 para 2025). Elimina node_modules y package-lock.json, luego ejecuta npm install.

Configuración de Sequelize y Postgres

Instala las dependencias necesarias para integrar Sequelize con Postgres:

npm install -g sequelize
npm install --save sequelize sequelize-typescript pg pg-hstore
npm install --save-dev @types/sequelize
npm install --save dotenv

Crea un módulo de base de datos con el comando:

nest generate module core/database

Interfaz de configuración de la base de datos

Dentro de src/core/database, crea una carpeta interfaces y un archivo dbConfig.interface.ts:

export interface IDatabaseConfigAttributes {
    username?: string;
    password?: string;
    database?: string;
    host?: string;
    port?: number | string;
    dialect?: string;
    urlDatabase?: string;
}

export interface IDatabaseConfig {
    development: IDatabaseConfigAttributes;
    test: IDatabaseConfigAttributes;
    production: IDatabaseConfigAttributes;
}

Configuración de entornos

Crea el archivo database.config.ts en src/core/database:

import * as dotenv from "dotenv";
import { IDatabaseConfig } from "./interfaces/dbConfig.interface";

dotenv.config();

export const databaseConfig: IDatabaseConfig = {
    development: {
        username: process.env.DB_USER,
        password: process.env.DB_PASS,
        database: process.env.DB_NAME_DEVELOPMENT,
        host: process.env.DB_HOST,
        port: process.env.DB_PORT,
        dialect: process.env.DB_DIALECT,
    },
    test: {
        username: process.env.DB_USER,
        password: process.env.DB_PASS,
        database: process.env.DB_NAME_TEST,
        host: process.env.DB_HOST,
        port: process.env.DB_PORT,
        dialect: process.env.DB_DIALECT,
    },
    production: {
        username: process.env.DB_USER,
        password: process.env.DB_PASS,
        database: process.env.DB_NAME_PRODUCTION,
        host: process.env.DB_HOST,
        dialect: process.env.DB_DIALECT,
    },
};

Archivo de entorno

En la raíz del proyecto, crea los archivos .env y .env.sample:

DB_HOST=localhost
DB_PORT=5432
DB_USER=tu_usuario
DB_PASS=tu_contraseña
DB_DIALECT=postgres
DB_NAME_TEST=test_db
DB_NAME_DEVELOPMENT=dev_db
DB_NAME_PRODUCTION=prod_db
JWTKEY=tu_clave_secreta
TOKEN_EXPIRATION=48h
BEARER=Bearer

Asegúrate de configurar .env con los valores correctos y añadirlo a .gitignore. Instala el paquete @nestjs/config para gestionar variables de entorno:

npm install --save @nestjs/config

Actualiza app.module.ts para cargar las variables de entorno globalmente:

import { Module } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";

@Module({
    imports: [ConfigModule.forRoot({ isGlobal: true })],
})
export class AppModule {}

Proveedor de base de datos

Crea database.providers.ts en src/core/database:

import { Sequelize } from "sequelize-typescript";
import { SEQUELIZE, DEVELOPMENT, TEST, PRODUCTION } from "../constants";
import { databaseConfig } from "./database.config";

export const databaseProviders = [
    {
        provide: SEQUELIZE,
        useFactory: async () => {
            let config;
            switch (process.env.NODE_ENV) {
                case DEVELOPMENT:
                    config = databaseConfig.development;
                    break;
                case TEST:
                    config = databaseConfig.test;
                    break;
                case PRODUCTION:
                    config = databaseConfig.production;
                    break;
                default:
                    config = databaseConfig.development;
            }
            const sequelize = new Sequelize(config);
            sequelize.addModels([]);
            await sequelize.sync();
            return sequelize;
        },
    },
];

Crea una carpeta constants en src/core con un archivo index.ts:

export const SEQUELIZE = "SEQUELIZE";
export const DEVELOPMENT = "development";
export const TEST = "test";
export const PRODUCTION = "production";

Actualiza database.module.ts:

import { Module } from "@nestjs/common";
import { databaseProviders } from "./database.providers";

@Module({
    providers: [...databaseProviders],
    exports: [...databaseProviders],
})
export class DatabaseModule {}

Importa el módulo en app.module.ts:

import { Module } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
import { DatabaseModule } from "./core/database/database.module";

@Module({
    imports: [ConfigModule.forRoot({ isGlobal: true }), DatabaseModule],
})
export class AppModule {}

Prefijo global para endpoints

Configura un prefijo global api/v1 en main.ts:

import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";

async function bootstrap() {
    const app = await NestFactory.create(AppModule);
    app.setGlobalPrefix("api/v1");
    await app.listen(3000);
}
bootstrap();

Módulo de usuarios

Crea el módulo de usuarios para gestionar operaciones relacionadas con usuarios:

nest generate module modules/users

Servicio de usuarios

Genera el servicio:

nest generate service modules/users

Modelo de usuario

Crea user.entity.ts en src/modules/users:

import { Table, Column, Model, DataType } from "sequelize-typescript";

@Table
export class User extends Model<User> {
    @Column({
        type: DataType.STRING,
        allowNull: false,
    })
    name: string;

    @Column({
        type: DataType.STRING,
        unique: true,
        allowNull: false,
    })
    email: string;

    @Column({
        type: DataType.STRING,
        allowNull: false,
    })
    password: string;

    @Column({
        type: DataType.ENUM,
        values: ["male", "female"],
        allowNull: false,
    })
    gender: string;
}

DTO de usuario

Crea una carpeta dto en src/modules/users y un archivo user.dto.ts:

export class UserDto {
    readonly name: string;
    readonly email: string;
    readonly password: string;
    readonly gender: string;
}

Proveedor de usuarios

Crea users.providers.ts en src/modules/users:

import { User } from "./user.entity";
import { USER_REPOSITORY } from "../../core/constants";

export const usersProviders = [
    {
        provide: USER_REPOSITORY,
        useValue: User,
    },
];

Añade la constante USER_REPOSITORY en src/core/constants/index.ts:

export const USER_REPOSITORY = "USER_REPOSITORY";

Actualiza users.module.ts:

import { Module } from "@nestjs/common";
import { UsersService } from "./users.service";
import { usersProviders } from "./users.providers";

@Module({
    providers: [UsersService, ...usersProviders],
    exports: [UsersService],
})
export class UsersModule {}

Servicio de usuarios

Actualiza users.service.ts:

import { Injectable, Inject } from "@nestjs/common";
import { User } from "./user.entity";
import { UserDto } from "./dto/user.dto";
import { USER_REPOSITORY } from "../../core/constants";

@Injectable()
export class UsersService {
    constructor(
        @Inject(USER_REPOSITORY) private readonly userRepository: typeof User
    ) {}

    async create(user: UserDto): Promise<User> {
        return await this.userRepository.create<User>(user);
    }

    async findOneByEmail(email: string): Promise<User> {
        return await this.userRepository.findOne<User>({ where: { email } });
    }

    async findOneById(id: number): Promise<User> {
        return await this.userRepository.findOne<User>({ where: { id } });
    }
}

Añade el modelo User a database.providers.ts:

import { Sequelize } from "sequelize-typescript";
import { SEQUELIZE, DEVELOPMENT, TEST, PRODUCTION } from "../constants";
import { databaseConfig } from "./database.config";
import { User } from "../../modules/users/user.entity";

export const databaseProviders = [
    {
        provide: SEQUELIZE,
        useFactory: async () => {
            let config;
            switch (process.env.NODE_ENV) {
                case DEVELOPMENT:
                    config = databaseConfig.development;
                    break;
                case TEST:
                    config = databaseConfig.test;
                    break;
                case PRODUCTION:
                    config = databaseConfig.production;
                    break;
                default:
                    config = databaseConfig.development;
            }
            const sequelize = new Sequelize(config);
            sequelize.addModels([User]);
            await sequelize.sync();
            return sequelize;
        },
    },
];

Módulo de autenticación

Crea el módulo de autenticación:

nest generate module modules/auth
nest generate service modules/auth
nest generate controller modules/auth

Estrategia local de Passport

Instala las dependencias necesarias:

npm install --save @nestjs/passport passport passport-local
npm install --save-dev @types/passport-local
npm install --save bcrypt

Crea local.strategy.ts en src/modules/auth:

import { Strategy } from "passport-local";
import { PassportStrategy } from "@nestjs/passport";
import { Injectable, UnauthorizedException } from "@nestjs/common";
import { AuthService } from "./auth.service";

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
    constructor(private readonly authService: AuthService) {
        super();
    }

    async validate(username: string, password: string): Promise<any> {
        const user = await this.authService.validateUser(username, password);
        if (!user) {
            throw new UnauthorizedException("Credenciales inválidas");
        }
        return user;
    }
}

Actualiza auth.module.ts:

import { Module } from "@nestjs/common";
import { PassportModule } from "@nestjs/passport";
import { AuthService } from "./auth.service";
import { AuthController } from "./auth.controller";
import { UsersModule } from "../users/users.module";
import { LocalStrategy } from "./local.strategy";

@Module({
    imports: [PassportModule, UsersModule],
    providers: [AuthService, LocalStrategy],
    controllers: [AuthController],
})
export class AuthModule {}

Servicio de autenticación

Actualiza auth.service.ts:

import { Injectable } from "@nestjs/common";
import * as bcrypt from "bcrypt";
import { UsersService } from "../users/users.service";

@Injectable()
export class AuthService {
    constructor(private readonly userService: UsersService) {}

    async validateUser(username: string, pass: string) {
        const user = await this.userService.findOneByEmail(username);
        if (!user) {
            return null;
        }
        const match = await this.comparePassword(pass, user.password);
        if (!match) {
            return null;
        }
        const { password, ...result } = user["dataValues"];
        return result;
    }

    private async comparePassword(enteredPassword, dbPassword) {
        const match = await bcrypt.compare(enteredPassword, dbPassword);
        return match;
    }
}

Estrategia JWT

Instala las dependencias para JWT:

npm install --save @nestjs/jwt passport-jwt
npm install --save-dev @types/passport-jwt

Crea jwt.strategy.ts en src/modules/auth:

import { ExtractJwt, Strategy } from "passport-jwt";
import { PassportStrategy } from "@nestjs/passport";
import { Injectable, UnauthorizedException } from "@nestjs/common";
import { UsersService } from "../users/users.service";

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
    constructor(private readonly userService: UsersService) {
        super({
            jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
            ignoreExpiration: false,
            secretOrKey: process.env.JWTKEY,
        });
    }

    async validate(payload: any) {
        const user = await this.userService.findOneById(payload.id);
        if (!user) {
            throw new UnauthorizedException("No autorizado");
        }
        return payload;
    }
}

Actualiza auth.module.ts para incluir JWT:

import { Module } from "@nestjs/common";
import { PassportModule } from "@nestjs/passport";
import { JwtModule } from "@nestjs/jwt";
import { AuthService } from "./auth.service";
import { AuthController } from "./auth.controller";
import { UsersModule } from "../users/users.module";
import { LocalStrategy } from "./local.strategy";
import { JwtStrategy } from "./jwt.strategy";

@Module({
    imports: [
        PassportModule,
        UsersModule,
        JwtModule.register({
            secret: process.env.JWTKEY,
            signOptions: { expiresIn: process.env.TOKEN_EXPIRATION },
        }),
    ],
    providers: [AuthService, LocalStrategy, JwtStrategy],
    controllers: [AuthController],
})
export class AuthModule {}

Métodos adicionales en AuthService

Actualiza auth.service.ts para incluir métodos de login y signup:

import { Injectable } from "@nestjs/common";
import * as bcrypt from "bcrypt";
import { JwtService } from "@nestjs/jwt";
import { UsersService } from "../users/users.service";

@Injectable()
export class AuthService {
    constructor(
        private readonly userService: UsersService,
        private readonly jwtService: JwtService
    ) {}

    async validateUser(username: string, pass: string) {
        const user = await this.userService.findOneByEmail(username);
        if (!user) {
            return null;
        }
        const match = await this.comparePassword(pass, user.password);
        if (!match) {
            return null;
        }
        const { password, ...result } = user["dataValues"];
        return result;
    }

    public async login(user) {
        const token = await this.generateToken(user);
        return { user, token };
    }

    public async create(user) {
        const pass = await this.hashPassword(user.password);
        const newUser = await this.userService.create({
            ...user,
            password: pass,
        });
        const { password, ...result } = newUser["dataValues"];
        const token = await this.generateToken(result);
        return { user: result, token };
    }

    private async generateToken(user) {
        const token = await this.jwtService.signAsync(user);
        return token;
    }

    private async hashPassword(password) {
        const hash = await bcrypt.hash(password, 10);
        return hash;
    }

    private async comparePassword(enteredPassword, dbPassword) {
        const match = await bcrypt.compare(enteredPassword, dbPassword);
        return match;
    }
}

Controlador de autenticación

Actualiza auth.controller.ts:

import { Controller, Body, Post, UseGuards, Request } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";
import { AuthService } from "./auth.service";
import { UserDto } from "../users/dto/user.dto";

@Controller("auth")
export class AuthController {
    constructor(private authService: AuthService) {}

    @UseGuards(AuthGuard("local"))
    @Post("login")
    async login(@Request() req) {
        return await this.authService.login(req.user);
    }

    @Post("signup")
    async signUp(@Body() user: UserDto) {
        return await this.authService.create(user);
    }
}

Validación de datos

Instala las dependencias para validación:

npm install --save class-validator class-transformer

Crea una carpeta pipes en src/core y un archivo validate.pipe.ts:

import {
    Injectable,
    ArgumentMetadata,
    BadRequestException,
    ValidationPipe,
    UnprocessableEntityException,
} from "@nestjs/common";

@Injectable()
export class ValidateInputPipe extends ValidationPipe {
    public async transform(value, metadata: ArgumentMetadata) {
        try {
            return await super.transform(value, metadata);
        } catch (e) {
            if (e instanceof BadRequestException) {
                throw new UnprocessableEntityException(
                    this.handleError(e.message.message)
                );
            }
        }
    }

    private handleError(errors) {
        return errors.map((error) => error.constraints);
    }
}

Añade el pipe globalmente en main.ts:

import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import { ValidateInputPipe } from "./core/pipes/validate.pipe";

async function bootstrap() {
    const app = await NestFactory.create(AppModule);
    app.setGlobalPrefix("api/v1");
    app.useGlobalPipes(new ValidateInputPipe());
    await app.listen(3000);
}
bootstrap();

Actualiza user.dto.ts para incluir validaciones:

import { IsNotEmpty, MinLength, IsEmail, IsEnum } from "class-validator";

enum Gender {
    MALE = "male",
    FEMALE = "female",
}

export class UserDto {
    @IsNotEmpty()
    readonly name: string;

    @IsNotEmpty()
    @IsEmail()
    readonly email: string;

    @IsNotEmpty()
    @MinLength(6)
    readonly password: string;

    @IsNotEmpty()
    @IsEnum(Gender, {
        message: "El género debe ser masculino o femenino",
    })
    readonly gender: Gender;
}

Guardia para usuarios únicos

Crea una carpeta guards en src/core y un archivo doesUserExist.guard.ts:

import {
    CanActivate,
    ExecutionContext,
    Injectable,
    ForbiddenException,
} from "@nestjs/common";
import { Observable } from "rxjs";
import { UsersService } from "../../modules/users/users.service";

@Injectable()
export class DoesUserExist implements CanActivate {
    constructor(private readonly userService: UsersService) {}

    canActivate(
        context: ExecutionContext
    ): boolean | Promise<boolean> | Observable<boolean> {
        const request = context.switchToHttp().getRequest();
        return this.validateRequest(request);
    }

    async validateRequest(request) {
        const userExist = await this.userService.findOneByEmail(
            request.body.email
        );
        if (userExist) {
            throw new ForbiddenException("El email ya existe");
        }
        return true;
    }
}

Actualiza auth.controller.ts para usar la guardia:

import { Controller, Body, Post, UseGuards, Request } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";
import { AuthService } from "./auth.service";
import { UserDto } from "../users/dto/user.dto";
import { DoesUserExist } from "../../core/guards/doesUserExist.guard";

@Controller("auth")
export class AuthController {
    constructor(private authService: AuthService) {}

    @UseGuards(AuthGuard("local"))
    @Post("login")
    async login(@Request() req) {
        return await this.authService.login(req.user);
    }

    @UseGuards(DoesUserExist)
    @Post("signup")
    async signUp(@Body() user: UserDto) {
        return await this.authService.create(user);
    }
}

Módulo de publicaciones

Crea el módulo de publicaciones:

nest generate module modules/posts
nest generate service modules/posts
nest generate controller modules/posts

Entidad de publicación

Crea post.entity.ts en src/modules/posts:

import {
    Table,
    Column,
    Model,
    DataType,
    ForeignKey,
    BelongsTo,
} from "sequelize-typescript";
import { User } from "../users/user.entity";

@Table
export class Post extends Model<Post> {
    @Column({
        type: DataType.STRING,
        allowNull: false,
    })
    title: string;

    @Column({
        type: DataType.TEXT,
        allowNull: false,
    })
    body: string;

    @ForeignKey(() => User)
    @Column({
        type: DataType.INTEGER,
        allowNull: false,
    })
    userId: number;

    @BelongsTo(() => User)
    user: User;
}

DTO de publicación

Crea una carpeta dto en src/modules/posts y un archivo post.dto.ts:

import { IsNotEmpty, MinLength } from "class-validator";

export class PostDto {
    @IsNotEmpty()
    @MinLength(4)
    readonly title: string;

    @IsNotEmpty()
    readonly body: string;
}

Proveedor de publicaciones

Crea posts.providers.ts en src/modules/posts:

import { Post } from "./post.entity";
import { POST_REPOSITORY } from "../../core/constants";

export const postsProviders = [
    {
        provide: POST_REPOSITORY,
        useValue: Post,
    },
];

Añade la constante POST_REPOSITORY en src/core/constants/index.ts:

export const POST_REPOSITORY = "POST_REPOSITORY";

Actualiza posts.module.ts:

import { Module } from "@nestjs/common";
import { PostsService } from "./posts.service";
import { PostsController } from "./posts.controller";
import { postsProviders } from "./posts.providers";

@Module({
    divers: [PostsService, ...postsProviders],
    controllers: [PostsController],
})
export class PostsModule {}

Añade el modelo Post a database.providers.ts:

import { Sequelize } from "sequelize-typescript";
import { SEQUELIZE, DEVELOPMENT, TEST, PRODUCTION } from "../constants";
import { databaseConfig } from "./database.config";
import { User } from "../../modules/users/user.entity";
import { Post } from "../../modules/posts/post.entity";

export const databaseProviders = [
    {
        provide: SEQUELIZE,
        useFactory: async () => {
            let config;
            switch (process.env.NODE_ENV) {
                case DEVELOPMENT:
                    config = databaseConfig.development;
                    break;
                case TEST:
                    config = databaseConfig.test;
                    break;
                case PRODUCTION:
                    config = databaseConfig.production;
                    break;
                default:
                    config = databaseConfig.development;
            }
            const sequelize = new Sequelize(config);
            sequelize.addModels([User, Post]);
            await sequelize.sync();
            return sequelize;
        },
    },
];

Servicio de publicaciones

Actualiza posts.service.ts:

import { Injectable, Inject } from "@nestjs/common";
import { Post } from "./post.entity";
import { PostDto } from "./dto/post.dto";
import { User } from "../users/user.entity";
import { POST_REPOSITORY } from "../../core/constants";

@Injectable()
export class PostsService {
    constructor(
        @Inject(POST_REPOSITORY) private readonly postRepository: typeof Post
    ) {}

    async create(post: PostDto, userId): Promise<Post> {
        return await this.postRepository.create<Post>({ ...post, userId });
    }

    async findAll(): Promise<Post[]> {
        return await this.postRepository.findAll<Post>({
            include: [{ model: User, attributes: { exclude: ["password"] } }],
        });
    }

    async findOne(id): Promise<Post> {
        return await this.postRepository.findOne({
            where: { id },
            include: [{ model: User, attributes: { exclude: ["password"] } }],
        });
    }

    async delete(id, userId) {
        return await this.postRepository.destroy({ where: { id, userId } });
    }

    async update(id, data, userId) {
        const [numberOfAffectedRows, [updatedPost]] =
            await this.postRepository.update(
                { ...data },
                { where: { id, userId }, returning: true }
            );
        return { numberOfAffectedRows, updatedPost };
    }
}

Controlador de publicaciones

Actualiza posts.controller.ts:

import {
    Controller,
    Get,
    Post,
    Put,
    Delete,
    Param,
    Body,
    NotFoundException,
    UseGuards,
    Request,
} from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";
import { PostsService } from "./posts.service";
import { Post as PostEntity } from "./post.entity";
import { PostDto } from "./dto/post.dto";

@Controller("posts")
export class PostsController {
    constructor(private readonly postService: PostsService) {}

    @Get()
    async findAll() {
        return await this.postService.findAll();
    }

    @Get(":id")
    async findOne(@Param("id") id: number): Promise<PostEntity> {
        const post = await this.postService.findOne(id);
        if (!post) {
            throw new NotFoundException("Publicación no encontrada");
        }
        return post;
    }

    @UseGuards(AuthGuard("jwt"))
    @Post()
    async create(@Body() post: PostDto, @Request() req): Promise<PostEntity> {
        return await this.postService.create(post, req.user.id);
    }

    @UseGuards(AuthGuard("jwt"))
    @Put(":id")
    async update(
        @Param("id") id: number,
        @Body() post: PostDto,
        @Request() req
    ): Promise<PostEntity> {
        const { numberOfAffectedRows, updatedPost } =
            await this.postService.update(id, post, req.user.id);
        if (numberOfAffectedRows === 0) {
            throw new NotFoundException("Publicación no encontrada");
        }
        return updatedPost;
    }

    @UseGuards(AuthGuard("jwt"))
    @Delete(":id")
    async remove(@Param("id") id: number, @Request() req) {
        const deleted = await this.postService.delete(id, req.user.id);
        if (deleted === 0) {
            throw new NotFoundException("Publicación no encontrada");
        }
        return "Eliminado exitosamente";
    }
}

Conclusiones

NestJS ofrece una estructura robusta para desarrollar aplicaciones backend con Node.js, facilitando la creación de APIs RESTful escalables y mantenibles. Este tutorial cubrió la configuración de una API de blog con autenticación, validación y protección de rutas, utilizando herramientas modernas como Sequelize, Passport y JWT. Al implementar módulos, servicios y controladores, se logra una arquitectura limpia y modular, ideal para proyectos de cualquier escala. Los desarrolladores pueden extender esta base para agregar funcionalidades avanzadas, como caching o WebSockets, aprovechando la flexibilidad de NestJS. Explora la documentación oficial de NestJS para profundizar en sus capacidades y optimizar tus proyectos.