NestJS 入门 — 学生管理系统模块
目标
演示 NestJS 核心概念:模块化(Module)、控制器(Controller)、服务(Service)、依赖注入(DI)、DTO 验证。
1. 创建项目
nest new nest-demo
cd nest-demo
2. 生成学生模块
nest g resource students
3. 完整代码
src/students/dto/create-student.dto.ts
import { IsString, IsInt, Min, Max, IsEmail, IsOptional } from 'class-validator';
export class CreateStudentDto {
@IsString()
name: string;
@IsInt()
@Min(18)
@Max(30)
age: number;
@IsEmail()
email: string;
@IsOptional()
@IsString()
major?: string;
}
src/students/students.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { CreateStudentDto } from './dto/create-student.dto';
import { UpdateStudentDto } from './dto/update-student.dto';
@Injectable()
export class StudentsService {
private students = [];
create(dto: CreateStudentDto) {
const student = { id: Date.now(), ...dto, createdAt: new Date() };
this.students.push(student);
return student;
}
findAll() {
return { count: this.students.length, data: this.students };
}
findOne(id: number) {
const student = this.students.find(s => s.id === id);
if (!student) throw new NotFoundException(`学生 #${id} 不存在`);
return student;
}
update(id: number, dto: UpdateStudentDto) {
const student = this.findOne(id);
Object.assign(student, dto);
return student;
}
remove(id: number) {
const index = this.students.findIndex(s => s.id === id);
if (index === -1) throw new NotFoundException(`学生 #${id} 不存在`);
this.students.splice(index, 1);
return { message: '已删除' };
}
}
src/students/students.controller.ts
import { Controller, Get, Post, Body, Param, Delete, Put, ParseIntPipe, Query } from '@nestjs/common';
import { StudentsService } from './students.service';
import { CreateStudentDto } from './dto/create-student.dto';
import { UpdateStudentDto } from './dto/update-student.dto';
@Controller('students')
export class StudentsController {
constructor(private readonly studentsService: StudentsService) {}
@Post()
create(@Body() dto: CreateStudentDto) {
return this.studentsService.create(dto);
}
@Get()
findAll() {
return this.studentsService.findAll();
}
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.studentsService.findOne(id);
}
@Put(':id')
update(@Param('id', ParseIntPipe) id: number, @Body() dto: UpdateStudentDto) {
return this.studentsService.update(id, dto);
}
@Delete(':id')
remove(@Param('id', ParseIntPipe) id: number) {
return this.studentsService.remove(id);
}
}
运行步骤
npm run start:dev
curl -X POST http:
-H "Content-Type: application/json" \
-d '{"name":"张三","age":22,"email":"zhangsan@univ.edu.cn","major":"计算机科学"}'
预期输出
GET /students → {"count":1,"data":[...]}
- DTO 自动验证:age < 18 或 email 格式错误时返回 400 及详细错误信息
- 访问不存在的学生返回 404:
{"statusCode":404,"message":"学生 #999 不存在"}
NestJS TypeORM CRUD + Swagger 文档
目标
用 TypeORM 操作 SQLite 数据库,配合 Swagger 自动生成交互式 API 文档。
模块代码
src/posts/entities/post.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm';
import { ApiProperty } from '@nestjs/swagger';
@Entity()
export class Post {
@ApiProperty({ description: '文章ID', example: 1 })
@PrimaryGeneratedColumn()
id: number;
@ApiProperty({ description: '标题', example: '毕设心得' })
@Column({ length: 100 })
title: string;
@ApiProperty({ description: '内容', example: '这是正文...' })
@Column('text')
content: string;
@ApiProperty({ description: '作者', example: '张三' })
@Column({ length: 50 })
author: string;
@ApiProperty({ description: '创建时间' })
@CreateDateColumn()
createdAt: Date;
}
src/posts/dto/create-post.dto.ts
import { ApiProperty } from '@nestjs/swagger';
import { IsString, MinLength, MaxLength } from 'class-validator';
export class CreatePostDto {
@ApiProperty({ description: '文章标题', minLength: 2, maxLength: 100 })
@IsString()
@MinLength(2)
@MaxLength(100)
title: string;
@ApiProperty({ description: '文章内容', minLength: 10 })
@IsString()
@MinLength(10)
content: string;
@ApiProperty({ description: '作者名', maxLength: 50 })
@IsString()
@MaxLength(50)
author: string;
}
src/posts/posts.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, Like } from 'typeorm';
import { Post } from './entities/post.entity';
import { CreatePostDto } from './dto/create-post.dto';
@Injectable()
export class PostsService {
constructor(
@InjectRepository(Post)
private postsRepo: Repository<Post>,
) {}
async create(dto: CreatePostDto): Promise<Post> {
return this.postsRepo.save(this.postsRepo.create(dto));
}
async findAll(page = 1, limit = 10, keyword?: string) {
const where = keyword
? [{ title: Like(`%${keyword}%`) }, { author: Like(`%${keyword}%`) }]
: {};
const [data, total] = await this.postsRepo.findAndCount({
where,
skip: (page - 1) * limit,
take: limit,
order: { createdAt: 'DESC' },
});
return { data, total, page, limit, totalPages: Math.ceil(total / limit) };
}
async findOne(id: number): Promise<Post> {
const post = await this.postsRepo.findOne({ where: { id } });
if (!post) throw new NotFoundException(`文章 #${id} 不存在`);
return post;
}
async update(id: number, dto: Partial<CreatePostDto>) {
const post = await this.findOne(id);
Object.assign(post, dto);
return this.postsRepo.save(post);
}
async remove(id: number) {
const post = await this.findOne(id);
return this.postsRepo.remove(post);
}
}
src/posts/posts.controller.ts
import { Controller, Get, Post, Body, Param, Delete, Put, Query, ParseIntPipe } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiQuery, ApiResponse } from '@nestjs/swagger';
import { PostsService } from './posts.service';
import { CreatePostDto } from './dto/create-post.dto';
@ApiTags('文章管理')
@Controller('posts')
export class PostsController {
constructor(private readonly postsService: PostsService) {}
@Post()
@ApiOperation({ summary: '创建文章' })
@ApiResponse({ status: 201, description: '创建成功' })
create(@Body() dto: CreatePostDto) {
return this.postsService.create(dto);
}
@Get()
@ApiOperation({ summary: '获取文章列表(支持分页+搜索)' })
@ApiQuery({ name: 'page', required: false, example: 1 })
@ApiQuery({ name: 'limit', required: false, example: 10 })
@ApiQuery({ name: 'q', required: false, description: '搜索关键词' })
findAll(
@Query('page') page = 1,
@Query('limit') limit = 10,
@Query('q') q?: string,
) {
return this.postsService.findAll(+page, +limit, q);
}
@Get(':id')
@ApiOperation({ summary: '获取单篇文章' })
findOne(@Param('id', ParseIntPipe) id: number) {
return this.postsService.findOne(id);
}
@Put(':id')
@ApiOperation({ summary: '更新文章' })
update(@Param('id', ParseIntPipe) id: number, @Body() dto: Partial<CreatePostDto>) {
return this.postsService.update(id, dto);
}
@Delete(':id')
@ApiOperation({ summary: '删除文章' })
remove(@Param('id', ParseIntPipe) id: number) {
return this.postsService.remove(id);
}
}
src/posts/posts.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { PostsService } from './posts.service';
import { PostsController } from './posts.controller';
import { Post } from './entities/post.entity';
@Module({
imports: [TypeOrmModule.forFeature([Post])],
controllers: [PostsController],
providers: [PostsService],
})
export class PostsModule {}
app.module.ts 数据库配置
TypeOrmModule.forRoot({
type: 'sqlite',
database: 'blog.db',
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true,
}),
运行
npm run start:dev
预期效果
- Swagger UI 展示所有 API,可直接点 Try it out 测试
- 分页查询支持 keyword 模糊搜索
- SQLite 零配置,
blog.db 文件随项目携带