install
source · Clone the upstream repo
git clone https://github.com/ComeOnOliver/skillshub
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/TerminalSkills/skills/nestjs" ~/.claude/skills/comeonoliver-skillshub-nestjs && rm -rf "$T"
manifest:
skills/TerminalSkills/skills/nestjs/SKILL.mdsource content
NestJS
NestJS provides a modular architecture with dependency injection, decorators for routing and validation, guards for auth, and interceptors for cross-cutting concerns.
Installation
# Create new NestJS project npm i -g @nestjs/cli nest new my-api cd my-api npm i @nestjs/typeorm typeorm pg class-validator class-transformer
Project Structure
# Standard NestJS project layout src/ ├── main.ts # Bootstrap ├── app.module.ts # Root module ├── articles/ │ ├── articles.module.ts # Feature module │ ├── articles.controller.ts │ ├── articles.service.ts │ ├── entities/article.entity.ts │ └── dto/ │ ├── create-article.dto.ts │ └── update-article.dto.ts ├── auth/ │ ├── auth.module.ts │ ├── auth.guard.ts │ └── auth.service.ts └── common/ ├── filters/ └── interceptors/
Module
// src/articles/articles.module.ts — feature module import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ArticlesController } from './articles.controller'; import { ArticlesService } from './articles.service'; import { Article } from './entities/article.entity'; @Module({ imports: [TypeOrmModule.forFeature([Article])], controllers: [ArticlesController], providers: [ArticlesService], exports: [ArticlesService], }) export class ArticlesModule {}
Entity
// src/articles/entities/article.entity.ts — TypeORM entity import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, ManyToOne } from 'typeorm'; import { User } from '../../users/entities/user.entity'; @Entity() export class Article { @PrimaryGeneratedColumn() id: number; @Column({ length: 200 }) title: string; @Column('text') body: string; @ManyToOne(() => User, (user) => user.articles) author: User; @CreateDateColumn() createdAt: Date; }
DTOs with Validation
// src/articles/dto/create-article.dto.ts — validated DTO import { IsString, IsNotEmpty, MaxLength } from 'class-validator'; export class CreateArticleDto { @IsString() @IsNotEmpty() @MaxLength(200) title: string; @IsString() @IsNotEmpty() body: string; }
Controller
// src/articles/articles.controller.ts — REST controller import { Controller, Get, Post, Body, Param, Delete, ParseIntPipe, UseGuards, Query, HttpCode, HttpStatus, } from '@nestjs/common'; import { ArticlesService } from './articles.service'; import { CreateArticleDto } from './dto/create-article.dto'; import { AuthGuard } from '../auth/auth.guard'; @Controller('articles') export class ArticlesController { constructor(private readonly articlesService: ArticlesService) {} @Get() findAll(@Query('page') page = 1, @Query('limit') limit = 20) { return this.articlesService.findAll(+page, +limit); } @Get(':id') findOne(@Param('id', ParseIntPipe) id: number) { return this.articlesService.findOne(id); } @Post() @UseGuards(AuthGuard) @HttpCode(HttpStatus.CREATED) create(@Body() dto: CreateArticleDto) { return this.articlesService.create(dto); } @Delete(':id') @UseGuards(AuthGuard) @HttpCode(HttpStatus.NO_CONTENT) remove(@Param('id', ParseIntPipe) id: number) { return this.articlesService.remove(id); } }
Service
// src/articles/articles.service.ts — business logic with DI import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Article } from './entities/article.entity'; import { CreateArticleDto } from './dto/create-article.dto'; @Injectable() export class ArticlesService { constructor( @InjectRepository(Article) private readonly repo: Repository<Article>, ) {} async findAll(page: number, limit: number) { return this.repo.find({ skip: (page - 1) * limit, take: limit, order: { createdAt: 'DESC' }, relations: ['author'], }); } async findOne(id: number) { const article = await this.repo.findOne({ where: { id }, relations: ['author'] }); if (!article) throw new NotFoundException(`Article #${id} not found`); return article; } async create(dto: CreateArticleDto) { const article = this.repo.create(dto); return this.repo.save(article); } async remove(id: number) { const result = await this.repo.delete(id); if (result.affected === 0) throw new NotFoundException(); } }
Guards
// src/auth/auth.guard.ts — JWT auth guard import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { Request } from 'express'; @Injectable() export class AuthGuard implements CanActivate { constructor(private jwtService: JwtService) {} async canActivate(context: ExecutionContext): Promise<boolean> { const request = context.switchToHttp().getRequest<Request>(); const token = request.headers.authorization?.replace('Bearer ', ''); if (!token) throw new UnauthorizedException(); try { request['user'] = await this.jwtService.verifyAsync(token); return true; } catch { throw new UnauthorizedException(); } } }
App Module and Bootstrap
// src/app.module.ts — root module with TypeORM config import { Module, ValidationPipe } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ArticlesModule } from './articles/articles.module'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', host: process.env.DB_HOST ?? 'localhost', port: 5432, database: process.env.DB_NAME ?? 'mydb', username: process.env.DB_USER ?? 'postgres', password: process.env.DB_PASSWORD ?? '', autoLoadEntities: true, synchronize: process.env.NODE_ENV !== 'production', }), ArticlesModule, ], }) export class AppModule {}
// src/main.ts — bootstrap with global pipes import { NestFactory } from '@nestjs/core'; import { ValidationPipe } from '@nestjs/common'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true })); app.enableCors(); await app.listen(3000); } bootstrap();
Testing
// src/articles/articles.service.spec.ts — unit test import { Test } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import { ArticlesService } from './articles.service'; import { Article } from './entities/article.entity'; describe('ArticlesService', () => { let service: ArticlesService; const mockRepo = { find: jest.fn().mockResolvedValue([]), create: jest.fn(), save: jest.fn() }; beforeEach(async () => { const module = await Test.createTestingModule({ providers: [ ArticlesService, { provide: getRepositoryToken(Article), useValue: mockRepo }, ], }).compile(); service = module.get(ArticlesService); }); it('returns articles', async () => { expect(await service.findAll(1, 20)).toEqual([]); }); });
Key Patterns
- One module per feature; import only what you need
- Use
withValidationPipe
to strip unknown propertieswhitelist: true - Use guards for auth, interceptors for response mapping, filters for exceptions
- Set
in production — use TypeORM migrations insteadsynchronize: false - Use
with@nestjs/config
for environment variablesConfigModule.forRoot()