大纲
- Nest
- 安装@nestjs/cli
- 创建nest项目
- Mysql与typeorm
- 安装mysql
- 定义数据库实体
- GraphQL
- 安装graphql
- 定义graphql 数据模型
- graphql query实现
- GraphQL 定义分页类型与实现
- resful分页实现
- graphql数据类型定义与分页实现
如看本文,默认你对nestjs有一定的了解了。
- Nestjs:docs.nestjs.com/
- Mysql:www.mysql.com/
- GraphGL:graphql.org/
模块依赖关系
一、Nest
1、安装nestjs
pnpm i -g @nestjs/cli
2、创建nest项目
nest new nest-mysql-graphql
3、nest入口加Logger,方便调试,src/main.ts
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { Logger } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
Logger.log('http://127.0.0.1:3000', '项目启动成功');
}
bootstrap();
二、Nest与Mysql
1、安装mysql
pnpm install @nestjs/typeorm typeorm mysql2 --save
链接mysql数据,如没报错,那恭喜你链接成功!
// src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
// 导出orm模块
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: '123456', // 数据库密码,自己定义的
database: 'nest-mysql-graphql', // 数据库名称,提前建好
entities: [],
synchronize: true,
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
2、使用 nest g resource 生成CRUD模块代码
nest g resource modules/user
在src/modules下生成user模块
user模块,依赖@nestjs/mapped-types,继续安装
pnpm install @nestjs/mapped-types --save
3、定义user实体,src/modules/user/entities/user.entity.ts
// src/modules/user/entities/user.entity.ts
import {
Entity,
Column,
PrimaryGeneratedColumn,
UpdateDateColumn,
CreateDateColumn,
} from 'typeorm';
@Entity('user')
export class UserEntity {
@PrimaryGeneratedColumn({
comment: '用户id',
})
id: number;
@Column({
type: 'varchar',
width: 255,
nullable: false,
comment: '用户名',
})
username: string;
@Column({
type: 'varchar',
width: 255,
nullable: true,
comment: '邮件',
})
email: string;
@Column({
type: 'varchar',
width: 255,
nullable: true,
comment: '密码',
})
password: string;
@CreateDateColumn({
name: 'created_at',
type: 'timestamp',
nullable: true,
comment: '添加时间',
})
createdAt: Date;
@UpdateDateColumn({
name: 'updated_at',
type: 'timestamp',
nullable: true,
comment: '更新时间',
})
updatedAt: Date;
}
4、在配置中加载user实体
// src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
// nest g resource modules/user 命令自动注入
import { UserModule } from './modules/user/user.module';
// 导入实体到typeorm配置中,数据库自动创建user表
import { UserEntity } from './modules/user/entities/user.entity';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'wujian798',
database: 'nest-mysql-graphql',
entities: [UserEntity],
synchronize: true,
}),
UserModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
自动创建user表
5、TypeOrmModule.forFeature加载user实体到user模块中
// src/modules/user/user.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { UserEntity } from './entities/user.entity';
@Module({
imports: [TypeOrmModule.forFeature([UserEntity])],
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}
6、user服务关联实体
// src/modules/user/user.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { UserEntity } from './entities/user.entity';
@Injectable()
export class UserService {
constructor(
@InjectRepository(UserEntity)
private readonly userRepository: Repository<UserEntity>,
) {}
create(createUserDto: CreateUserDto) {
this.userRepository.create(createUserDto);
return this.userRepository.save(createUserDto);
}
findAll() {
return this.userRepository.find();
}
findOne(id: number) {
return this.userRepository.findOne({
where: { id },
});
}
update(id: number, updateUserDto: UpdateUserDto) {
return this.userRepository.update(id, updateUserDto);
}
remove(id: number) {
return this.userRepository.delete(id);
}
}
postman测试curd,结果如下:
POST创建PUT编辑GET列表GET列表项DELETE删除
所有接口到正常,到这里nest框架mvp已完成,我们继续吧
三、nest与graphql
1、安装graphql
pnpm i @nestjs/graphql @nestjs/apollo graphql apollo-server-express
2、定义graphql 数据模型,可以与共用user实体类
// // src/modules/user/entities/user.entity.ts
import {
Entity,
Column,
PrimaryGeneratedColumn,
UpdateDateColumn,
CreateDateColumn,
} from 'typeorm';
import { Field, Int, ObjectType } from '@nestjs/graphql';
@ObjectType()
@Entity('user')
export class UserEntity {
@Field(() => Int)
@PrimaryGeneratedColumn({
comment: '用户id',
})
id: number;
@Field()
@Column({
type: 'varchar',
width: 255,
nullable: false,
comment: '用户名',
})
username: string;
@Field()
@Column({
type: 'varchar',
width: 255,
nullable: true,
comment: '邮件',
})
email: string;
@Field()
@Column({
type: 'varchar',
width: 255,
nullable: true,
comment: '密码',
})
password: string;
@Field()
@CreateDateColumn({
name: 'created_at',
type: 'timestamp',
nullable: true,
comment: '添加时间',
})
createdAt: Date;
@Field()
@UpdateDateColumn({
name: 'updated_at',
type: 'timestamp',
nullable: true,
comment: '更新时间',
})
updatedAt: Date;
}
3、在user文件夹新增graphql resolver
// src/modules/user/user.resolver.ts
import { UserEntity } from './entities/user.entity';
import { UserService } from './user.service';
import { Resolver, Query, Args } from '@nestjs/graphql';
@Resolver(() => UserEntity)
export class UserResolver {
constructor(private readonly userService: UserService) {}
@Query(() => [UserEntity], { name: 'users', nullable: true })
users(): Promise<UserEntity[]> {
return this.userService.findAll();
}
@Query(() => UserEntity, { nullable: true })
user(@Args('id') id: number): Promise<UserEntity> {
return this.userService.findOne(id);
}
}
3、graphql resolver导入 module providers中
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { UserEntity } from './entities/user.entity';
import { UserResolver } from './user.resolver';
@Module({
imports: [TypeOrmModule.forFeature([UserEntity])],
controllers: [UserController],
providers: [UserResolver, UserService],
})
export class UserModule {}
4、配置graphql
// src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
// nest g resource modules/user 命令自动注入
import { UserModule } from './modules/user/user.module';
// 导入实体到typeorm配置中,数据库自动创建user表
import { UserEntity } from './modules/user/entities/user.entity';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'wujian798',
database: 'nest-mysql-graphql',
entities: [UserEntity],
synchronize: true,
}),
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
}),
UserModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
查看:http://127.0.0.1:3000/graphql 可以该可视化工具测试graphql
query {
user(id: 3) {
id,
username,
password,
email,
}
}
- 测试一条数据
- 测试多数据
- 测试集合数据
写到这,mysql到grapql链路已完成,本想就此结束。再想想自己处理分页是,遇到了很多炕,在掘金、google、github找了很久,没找到满意的demo(解决方案),后来还是回到nest官网上找到解决方法。
四、 GraphQL 定义分页类型与实现
1、RESTful分页实现,
在 user.service.ts 添加分页服务方法 findAndCount
// src/modules/user/user.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { UserEntity } from './entities/user.entity';
import { BaseService, IPaginationOptions } from '../../globals/base.service';
@Injectable()
export class UserService extends BaseService {
constructor(
@InjectRepository(UserEntity)
private readonly userRepository: Repository<UserEntity>,
) {
super(userRepository);
}
// 添加分页服务方法
findAndCount(options?: IPaginationOptions) {
return this.findListAndPage(options);
}
}
分页方法提取到base.service.ts中,方便复用。
// src/globals/base.service.ts
import { Repository, FindOptionsRelations } from 'typeorm';
export interface IPagination {
page?: number;
size?: number;
}
// 分页返回体数据结构
export interface IPaginationResponse<T = any> {
list: Array<T>;
pagination: IPagination;
total: number;
}
export interface IPaginationOptions {
pagination?: IPagination;
order?: object;
where?: object;
relations?: FindOptionsRelations<any>;
select?: object;
}
export class BaseService {
constructor(private readonly currentRepository: Repository<any>) {}
async findListAndPage(
options: IPaginationOptions,
): Promise<IPaginationResponse> {
const DEFOULT_PAGE = 1;
const DEFOULT_SIZE = 20;
const {
pagination = { page: DEFOULT_PAGE, size: DEFOULT_SIZE },
order = {},
where = {},
relations = {},
select = {},
} = options || {};
const { page = DEFOULT_PAGE, size = DEFOULT_SIZE } = pagination;
const [list, total]: [Array<any>, number] =
await this.currentRepository.findAndCount({
take: size,
skip: (page - 1) * size,
order,
where,
relations,
select,
});
return {
list,
pagination: {
page,
size,
},
total,
};
}
}
在user.controller.ts中添加分页方法 findAndCount
// src/modules/user/user.controller.ts
import {
Controller,
Get,
Post,
Body,
Put,
Param,
Delete,
Query,
} from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { getNumber } from '../../utils';
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {}
@Get('page')
findAndCount(@Query() query: { page?: number; size?: number } = {}) {
const { page, size } = query;
return this.userService.findAndCount({
pagination: { page: getNumber(page), size: getNumber(size) },
});
}
}
resful接口测试分页功能,如下图
2、GraphQL数据类型定义与分页实现
在user.resolver.ts中添加分页query userList
// src/modules/user/user.resolver.ts
import { UserEntity } from './entities/user.entity';
import { UserService } from './user.service';
import { Resolver, Query, Args } from '@nestjs/graphql';
// 定义分页数据结构
import { UserListPaginated } from './dto/userList.paginated.gql';
@Resolver(() => UserEntity)
export class UserResolver {
constructor(private readonly userService: UserService) {}
@Query(() => UserListPaginated, { name: 'userList', nullable: true })
userList(
@Args('page', {
type: () => Int,
defaultValue: 1,
})
page?: number,
@Args('size', {
type: () => Int,
defaultValue: 20,
nullable: true,
})
size?: number,
): Promise<UserListPaginated> {
return this.userService.findListAndPage({
pagination: {
page,
size,
},
});
}
}
[重要] 定义分页数据结构
// src/modules/user/dto/userList.paginated.gql.ts
import { ObjectType } from '@nestjs/graphql';
import { UserEntity } from '../entities/user.entity';
import { Paginated } from '../../../globals/paginated.gql';
@ObjectType()
export class UserListPaginated extends Paginated<UserEntity>(UserEntity) {}
[重要]分页方法提取到paginated.gql.ts中,方便复用。
// src/globals/paginated.gql.ts
import { Field, ObjectType, Int } from '@nestjs/graphql';
import { Type } from '@nestjs/common';
interface IPagination {
page?: number;
size?: number;
}
export interface IPaginatedType<T> {
list: T[];
total: number;
pagination: IPagination;
}
export function Paginated<T>(classRef: Type<T>): Type<IPaginatedType<T>> {
@ObjectType(`${classRef.name}Pagination`)
abstract class Pagination {
@Field(() => Number)
size: number;
@Field(() => Number)
page: number;
}
@ObjectType({ isAbstract: true })
abstract class PaginatedType implements IPaginatedType<T> {
@Field(() => Pagination, { nullable: true })
pagination: Pagination;
@Field(() => [classRef], { nullable: true })
list: T[];
@Field(() => Int)
total: number;
}
return PaginatedType as Type<IPaginatedType<T>>;
}
测试:http://127.0.0.1:3000/graphql
- 默认参数测试
- 添加上参数
(page:1,size:2)测试
五、 总结
个人还是比较Nestjs与GraphQL的,nestjs入门比较简单的,自己用nestjs写一个博客,就能入门,但是要精通它(特别是它的周边配套,服务端的知识,太多了)还是很难的。 最近自己在写一下小项目,一直在学习,希望能与jym交流,有问题留言。
实例代码:github.com/wujian-xyz/… ,希望能帮到大家。