260223:1415 20260223 nextJS & nestJS Best pratices
All checks were successful
Build and Deploy / deploy (push) Successful in 4m44s
All checks were successful
Build and Deploy / deploy (push) Successful in 4m44s
This commit is contained in:
191
.agents/skills/nestjs-best-practices/rules/api-versioning.md
Normal file
191
.agents/skills/nestjs-best-practices/rules/api-versioning.md
Normal file
@@ -0,0 +1,191 @@
|
||||
---
|
||||
title: Use API Versioning for Breaking Changes
|
||||
impact: MEDIUM
|
||||
impactDescription: Versioning allows you to evolve APIs without breaking existing clients
|
||||
tags: api, versioning, breaking-changes, compatibility
|
||||
---
|
||||
|
||||
## Use API Versioning for Breaking Changes
|
||||
|
||||
Use NestJS built-in versioning when making breaking changes to your API. Choose a versioning strategy (URI, header, or media type) and apply it consistently. This allows old clients to continue working while new clients use updated endpoints.
|
||||
|
||||
**Incorrect (breaking changes without versioning):**
|
||||
|
||||
```typescript
|
||||
// Breaking changes without versioning
|
||||
@Controller('users')
|
||||
export class UsersController {
|
||||
@Get(':id')
|
||||
async findOne(@Param('id') id: string): Promise<User> {
|
||||
// Original response: { id, name, email }
|
||||
// Later changed to: { id, firstName, lastName, emailAddress }
|
||||
// Old clients break!
|
||||
return this.usersService.findOne(id);
|
||||
}
|
||||
}
|
||||
|
||||
// Manual versioning in routes
|
||||
@Controller('v1/users')
|
||||
export class UsersV1Controller {}
|
||||
|
||||
@Controller('v2/users')
|
||||
export class UsersV2Controller {}
|
||||
// Inconsistent, error-prone, hard to maintain
|
||||
```
|
||||
|
||||
**Correct (use NestJS built-in versioning):**
|
||||
|
||||
```typescript
|
||||
// Enable versioning in main.ts
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
||||
// URI versioning: /v1/users, /v2/users
|
||||
app.enableVersioning({
|
||||
type: VersioningType.URI,
|
||||
defaultVersion: '1',
|
||||
});
|
||||
|
||||
// Or header versioning: X-API-Version: 1
|
||||
app.enableVersioning({
|
||||
type: VersioningType.HEADER,
|
||||
header: 'X-API-Version',
|
||||
defaultVersion: '1',
|
||||
});
|
||||
|
||||
// Or media type: Accept: application/json;v=1
|
||||
app.enableVersioning({
|
||||
type: VersioningType.MEDIA_TYPE,
|
||||
key: 'v=',
|
||||
defaultVersion: '1',
|
||||
});
|
||||
|
||||
await app.listen(3000);
|
||||
}
|
||||
|
||||
// Version-specific controllers
|
||||
@Controller('users')
|
||||
@Version('1')
|
||||
export class UsersV1Controller {
|
||||
@Get(':id')
|
||||
async findOne(@Param('id') id: string): Promise<UserV1Response> {
|
||||
const user = await this.usersService.findOne(id);
|
||||
// V1 response format
|
||||
return {
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Controller('users')
|
||||
@Version('2')
|
||||
export class UsersV2Controller {
|
||||
@Get(':id')
|
||||
async findOne(@Param('id') id: string): Promise<UserV2Response> {
|
||||
const user = await this.usersService.findOne(id);
|
||||
// V2 response format with breaking changes
|
||||
return {
|
||||
id: user.id,
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
emailAddress: user.email,
|
||||
createdAt: user.createdAt,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Per-route versioning - different versions for different routes
|
||||
@Controller('users')
|
||||
export class UsersController {
|
||||
@Get()
|
||||
@Version('1')
|
||||
findAllV1(): Promise<UserV1Response[]> {
|
||||
return this.usersService.findAllV1();
|
||||
}
|
||||
|
||||
@Get()
|
||||
@Version('2')
|
||||
findAllV2(): Promise<UserV2Response[]> {
|
||||
return this.usersService.findAllV2();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@Version(['1', '2']) // Same handler for multiple versions
|
||||
findOne(@Param('id') id: string): Promise<User> {
|
||||
return this.usersService.findOne(id);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@Version(VERSION_NEUTRAL) // Available in all versions
|
||||
create(@Body() dto: CreateUserDto): Promise<User> {
|
||||
return this.usersService.create(dto);
|
||||
}
|
||||
}
|
||||
|
||||
// Shared service with version-specific logic
|
||||
@Injectable()
|
||||
export class UsersService {
|
||||
async findOne(id: string, version: string): Promise<any> {
|
||||
const user = await this.repo.findOne({ where: { id } });
|
||||
|
||||
if (version === '1') {
|
||||
return this.toV1Response(user);
|
||||
}
|
||||
return this.toV2Response(user);
|
||||
}
|
||||
|
||||
private toV1Response(user: User): UserV1Response {
|
||||
return {
|
||||
id: user.id,
|
||||
name: `${user.firstName} ${user.lastName}`,
|
||||
email: user.email,
|
||||
};
|
||||
}
|
||||
|
||||
private toV2Response(user: User): UserV2Response {
|
||||
return {
|
||||
id: user.id,
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
emailAddress: user.email,
|
||||
createdAt: user.createdAt,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Controller extracts version
|
||||
@Controller('users')
|
||||
export class UsersController {
|
||||
@Get(':id')
|
||||
async findOne(
|
||||
@Param('id') id: string,
|
||||
@Headers('X-API-Version') version: string = '1',
|
||||
): Promise<any> {
|
||||
return this.usersService.findOne(id, version);
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecation strategy - mark old versions as deprecated
|
||||
@Controller('users')
|
||||
@Version('1')
|
||||
@UseInterceptors(DeprecationInterceptor)
|
||||
export class UsersV1Controller {
|
||||
// All V1 routes will include deprecation warning
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class DeprecationInterceptor implements NestInterceptor {
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||
const response = context.switchToHttp().getResponse();
|
||||
response.setHeader('Deprecation', 'true');
|
||||
response.setHeader('Sunset', 'Sat, 1 Jan 2025 00:00:00 GMT');
|
||||
response.setHeader('Link', '</v2/users>; rel="successor-version"');
|
||||
|
||||
return next.handle();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Reference: [NestJS Versioning](https://docs.nestjs.com/techniques/versioning)
|
||||
Reference in New Issue
Block a user