Skip to content
Back to skills

Tpl Backend Nestjs Typescript

ASecurity

Template do pack (backend/07-nestjs-typescript.md). Orienta o agente em APIs, servicos e arquitetura backend alinhado a esse contexto.

  • 11 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 8, 2026
databasestypescriptgosqlnodetestingapidatabasebackend

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 8, 2026

npx -y skills add artubss/SKILLS-CLAUDE-CODE --skill tpl-backend-nestjs-typescript --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Tpl Backend Nestjs Typescript?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Tpl Backend Nestjs Typescript
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/artubss-tpl-backend-nestjs-typescript/badge)](https://www.skillsdirectory.com/skills/artubss-tpl-backend-nestjs-typescript)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: tpl-backend-nestjs-typescript
description: Template do pack (backend/07-nestjs-typescript.md). Orienta o agente em APIs, servicos e arquitetura backend alinhado a esse contexto.
metadata:
  version: 1.0.0
  source_template: backend/07-nestjs-typescript.md
  generated_by: install_pack_templates_as_claude_skills
---

# PROJECT: NestJS REST API

Skill gerado a partir do pack `templates-claude-code`. Arquivo de origem: `backend/07-nestjs-typescript.md`. Use como baseline e adapte ao projeto antes de mudancas grandes.

## Conteudo do template

## STACK

| Technology | Version | Purpose |
|------------|---------|---------|
| Node.js | 22.x | Runtime |
| TypeScript | 5.4+ | Language |
| NestJS | 10.x | Framework |
| PostgreSQL | 16 | Primary database |
| Prisma | 5.x | ORM + migrations |
| Passport | 0.7+ | Auth strategies |
| passport-jwt | 4.x | JWT strategy |
| @nestjs/jwt | 10.x | JWT module |
| class-validator | 0.14+ | DTO validation |
| class-transformer | 0.5+ | Request transformation |
| Jest | 29.x | Unit tests |
| Supertest | 7.x | e2e tests |
| @nestjs/testing | 10.x | Test module utilities |

---

## PROJECT STRUCTURE

```
src/
├── app.module.ts           # Root module — imports feature modules
├── main.ts                 # Bootstrap: create app, pipes, guards, listen
├── modules/
│   ├── auth/
│   │   ├── auth.module.ts
│   │   ├── auth.controller.ts
│   │   ├── auth.service.ts
│   │   ├── strategies/
│   │   │   ├── jwt.strategy.ts
│   │   │   └── jwt-refresh.strategy.ts
│   │   ├── guards/
│   │   │   ├── jwt-auth.guard.ts
│   │   │   └── roles.guard.ts
│   │   └── decorators/
│   │       ├── current-user.decorator.ts
│   │       └── roles.decorator.ts
│   └── users/
│       ├── users.module.ts
│       ├── users.controller.ts
│       ├── users.service.ts
│       ├── users.repository.ts  # Wraps Prisma for users domain
│       ├── dto/
│       │   ├── create-user.dto.ts
│       │   ├── update-user.dto.ts
│       │   └── user-response.dto.ts
│       └── interceptors/
│           └── user-transform.interceptor.ts
├── common/
│   ├── decorators/
│   ├── filters/
│   │   └── http-exception.filter.ts
│   ├── interceptors/
│   │   └── transform.interceptor.ts   # Wraps responses in { data, meta } envelope
│   └── pipes/
│       └── parse-uuid.pipe.ts
├── database/
│   └── prisma.service.ts    # Injectable Prisma client
└── config/
    └── configuration.ts     # Typed config with @nestjs/config
test/
├── app.e2e-spec.ts
└── jest-e2e.json
```

---

## ARCHITECTURE RULES

- **Controller → Service → Repository (→ Prisma)** — never skip
- Controllers: only receive request, call service, return response — no business logic
- Services: business logic only — no HTTP types, no direct Prisma import
- Repositories: Prisma wrapper for domain — no business logic, no validators
- Every module imports only what it needs — avoid importing `AppModule` in tests
- Global pipes, guards, filters, and interceptors registered in `main.ts` (not per-controller)
- DTOs are interfaces for validation only — business objects are separate
- `class-transformer` `plainToInstance` used in interceptors to strip sensitive fields

---

## NESTJS MODULE STRUCTURE

### When to Create a New Module

Create a new module when the domain:
1. Has its own database entity (table/model)
2. Has at least one controller endpoint
3. Has business logic that doesn't belong to an existing module

**Do NOT** create a module for:
- Simple utility functions → put in `common/`
- Config providers → use `@nestjs/config`
- Database access only → add to existing module's repository

### Module Template

```typescript
// src/modules/posts/posts.module.ts
import { Module } from '@nestjs/common'
import { PostsController } from './posts.controller'
import { PostsService } from './posts.service'
import { PostsRepository } from './posts.repository'
import { DatabaseModule } from '../../database/database.module'

@Module({
  imports: [DatabaseModule],   // Only import what this module needs
  controllers: [PostsController],
  providers: [PostsService, PostsRepository],
  exports: [PostsService],     // Only export if other modules need this service
})
export class PostsModule {}
```

---

## DECORATOR RULES

### Guards

Guards run BEFORE interceptors and pipes. Use `@UseGuards` on controller or route level; global guards registered in `main.ts`.

```typescript
// src/modules/auth/guards/jwt-auth.guard.ts
import { Injectable, ExecutionContext, UnauthorizedException } from '@nestjs/common'
import { AuthGuard } from '@nestjs/passport'
import { Reflector } from '@nestjs/core'
import { IS_PUBLIC_KEY } from '../decorators/public.decorator'

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
  constructor(private reflector: Reflector) {
    super()
  }

  canActivate(context: ExecutionContext) {
    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
      context.getHandler(),
      context.getClass(),
    ])
    if (isPublic) return true
    return super.canActivate(context)
  }
}

// src/modules/auth/guards/roles.guard.ts
@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const requiredRoles = this.reflector.getAllAndOverride<Role[]>('roles', [
      context.getHandler(),
      context.getClass(),
    ])
    if (!requiredRoles?.length) return true
    const { user } = context.switchToHttp().getRequest()
    return requiredRoles.includes(user.role)
  }
}
```

### Custom Decorators

```typescript
// src/modules/auth/decorators/current-user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common'

export const CurrentUser = createParamDecorator(
  (_data: unknown, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest()
    return request.user  // injected by JwtStrategy.validate()
  },
)

// src/modules/auth/decorators/roles.decorator.ts
import { SetMetadata } from '@nestjs/common'
export const Roles = (...roles: string[]) => SetMetadata('roles', roles)

// src/common/decorators/public.decorator.ts
import { SetMetadata } from '@nestjs/common'
export const IS_PUBLIC_KEY = 'isPublic'
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true)
```

### Usage in Controller

```typescript
// src/modules/users/users.controller.ts
import { Controller, Get, Patch, Body, HttpCode } from '@nestjs/common'
import { CurrentUser } from '../auth/decorators/current-user.decorator'
import { Roles } from '../auth/decorators/roles.decorator'
import { UpdateUserDto } from './dto/update-user.dto'
import { UsersService } from './users.service'

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

  @Get('me')
  getMe(@CurrentUser() user: AuthUser) {
    return this.usersService.findById(user.id)
  }

  @Patch('me')
  @HttpCode(200)
  updateMe(@CurrentUser() user: AuthUser, @Body() dto: UpdateUserDto) {
    return this.usersService.update(user.id, dto)
  }

  @Get()
  @Roles('admin')
  findAll() {
    return this.usersService.findAll()
  }
}
```

### DTOs with class-validator

```typescript
// src/modules/users/dto/create-user.dto.ts
import { IsEmail, IsString, MinLength, IsEnum, IsOptional } from 'class-validator'
import { Transform } from 'class-transformer'

export class CreateUserDto {
  @IsEmail({}, { message: 'Invalid email format' })
  @Transform(({ value }) => value?.toLowerCase().trim())
  email: string

  @IsString()
  @MinLength(2, { message: 'Name must be at least 2 characters' })
  name: string

  @IsString()
  @MinLength(8, { message: 'Password must be at least 8 characters' })
  password: string

  @IsEnum(['admin', 'user'], { message: 'role must be admin or user' })
  @IsOptional()
  role?: 'admin' | 'user' = 'user'
}
```

### Response Transform Interceptor

```typescript
// src/common/interceptors/transform.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'
import { map, Observable } from 'rxjs'
import { plainToInstance } from 'class-transformer'

export interface Response<T> {
  data: T
  timestamp: string
}

@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
  intercept(_context: ExecutionContext, next: CallHandler<T>): Observable<Response<T>> {
    return next.handle().pipe(
      map(data => ({
        data,
        timestamp: new Date().toISOString(),
      })),
    )
  }
}
```

---

## GLOBAL SETUP IN MAIN.TS

```typescript
// src/main.ts
import { NestFactory, Reflector } from '@nestjs/core'
import { ValidationPipe, ClassSerializerInterceptor } from '@nestjs/common'
import { AppModule } from './app.module'
import { JwtAuthGuard } from './modules/auth/guards/jwt-auth.guard'
import { RolesGuard } from './modules/auth/guards/roles.guard'
import { TransformInterceptor } from './common/interceptors/transform.interceptor'
import { HttpExceptionFilter } from './common/filters/http-exception.filter'

async function bootstrap() {
  const app = await NestFactory.create(AppModule)
  const reflector = app.get(Reflector)

  app.setGlobalPrefix('api/v1')
  app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true }))
  app.useGlobalGuards(new JwtAuthGuard(reflector), new RolesGuard(reflector))
  app.useGlobalInterceptors(new ClassSerializerInterceptor(reflector), new TransformInterceptor())
  app.useGlobalFilters(new HttpExceptionFilter())

  await app.listen(process.env.PORT ?? 3000)
}
bootstrap()
```

---

## ROUTING TABLE

| Trigger | Method | Route | Auth | Controller Action |
|---------|--------|-------|------|------------------|
| Register | POST | `/api/v1/auth/register` | Public | AuthController.register |
| Login | POST | `/api/v1/auth/login` | Public | AuthController.login |
| Refresh token | POST | `/api/v1/auth/refresh` | Refresh JWT | AuthController.refresh |
| Get own profile | GET | `/api/v1/users/me` | JWT | UsersController.getMe |
| Update profile | PATCH | `/api/v1/users/me` | JWT | UsersController.updateMe |
| Change password | POST | `/api/v1/users/me/password` | JWT | UsersController.changePassword |
| List all users | GET | `/api/v1/users` | JWT + admin role | UsersController.findAll |
| Get user by ID | GET | `/api/v1/users/:id` | JWT + admin role | UsersController.findOne |
| Delete user | DELETE | `/api/v1/users/:id` | JWT + admin role | UsersController.remove |

---

## QUALITY GATES

Before opening a PR, verify ALL of the following:

- [ ] `npx tsc --noEmit` passes — zero type errors
- [ ] `jest --passWithNoTests` passes — unit and e2e
- [ ] `ValidationPipe({ whitelist: true })` active — unknown properties stripped
- [ ] All new DTOs use `class-validator` decorators — no manual validation in controllers
- [ ] Response DTOs use `@Exclude()` on sensitive fields (`password`, `hashedToken`)
- [ ] Guards cover all endpoints — `@Public()` explicitly applied where auth is optional
- [ ] No direct Prisma usage in controllers or services — goes through repository
- [ ] Module only exports what other modules actually consume
- [ ] No circular imports between modules — use `forwardRef()` only as last resort
- [ ] `nest build` passes without errors

---

## FORBIDDEN

- **NEVER** use `@Injectable()` without including the class in a module's `providers` array
- **NEVER** import `PrismaService` directly into controllers — use repositories
- **NEVER** use `req.body` directly in controllers — always use typed DTOs
- **NEVER** bypass `ValidationPipe` by omitting `@Body()` decorator
- **NEVER** use `forwardRef()` as a first solution — redesign the dependency instead
- **NEVER** use `@Global()` module decorator except for `DatabaseModule` and `ConfigModule`
- **NEVER** throw raw `Error` — always throw `HttpException` subclasses (`NotFoundException`, etc.)
- **NEVER** put business logic in guards — guards only answer "can this pass?" (boolean)
- **NEVER** put auth logic in services — use guards and strategies (Passport)
- **NEVER** expose the Prisma model type directly in API responses — use response DTOs

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

View all in databases →

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.