最近在用graphql
来重写原来的数据查询方式,难免要重新学习如何编写增删改查,网上对于查的文档比较多,但是对于增删改的记录都比较少,本文旨在记录node server端的体验记录。
阅读本文之前,请先确认已经接入了graphql,可先参照例子
因使用的框架是Nest.js,不免要看文档是如何使用的,文档中提供了两种编写方式,一种是直接编写SDL,一种是通过代码的方式来生成SDL,所以本文采用第二种方案,type-graphql
。
我们先简单定义一个User实体。
import { Column, Entity, PrimaryGeneratedColumn, Unique } from 'typeorm';
import { Field, ID, ObjectType } from 'type-graphql';
@ObjectType()
@Entity()
@Unique(['username'])
export class User {
@Field(type => ID)
@PrimaryGeneratedColumn()
id: number;
@Field()
@Column()
username: string;
@Field()
@Column()
password: string;
}
可以看出User实体就两个字段,一个username,一个password,剩下的那个是自增长的id。
从行首引入的包可以看出,我是typeorm和graphql的字段定义是放在一起的,这样可以节省代码,也不需要两边维护。
这样一个简单的实体就定义好了。
接下来是如何使用graphql和typeorm
结合在一起。
我们先来确认一下接入graphql需要新建的定义,resolver
,这是原来使用typeorm
所没有的概念。
resolver
的使用意义就是建立一个graphql的数据映射。
我目前使用的数据链路是这样的:
graphql client => resolver => service => typeorm
这么一个链路还是比较直观的,可以很简单的分析出来,新建一个resolver,需要注入service依赖。
查
我们先简单写一个查
import { NotFoundException } from '@nestjs/common';
import { Query, Resolver } from '@nestjs/graphql';
import { User } from './user.entity';
import { UserService } from './user.service';
@Resolver(of => User) // 对该类进行Resolver定义,类似REST框架中的控制器
export class UserResolver {
constructor(
private readonly userService: UserService, // 注入UserService
) {}
@Query(returns => User) // 添加Query装饰器,把方法标记为graphql查询
async user(@Args('id') id: number): Promise<User> { // 定义入参出参
const user = await this.userService.findOne(id); // 使用service进行查询
if (!user) {
throw new NotFoundException(id);
}
return user; // 返回user
}
@Query(returns => User) // 添加Query装饰器,把方法标记为graphql查询
async userByUsername(@Args('username') username: string): Promise<User> { // 定义入参出参
const user = await this.userService.findOneByUsername(username); // 使用service进行查询
if (!user) {
throw new NotFoundException(username);
}
return user; // 返回user
}
}
上面的代码还是比较清晰易懂的,跟用控制器时候的开发体验比较一致,获取入参,通过入参查询数据,之后返回数据。
那么经过上述定义后,type-graphql会自动生成schema。(感叹号是必填的意思)
type Query {
user(id: Float!): User!
userByUsername(username: String!): User!
}
type User {
id: ID!
username: String!
password: String!
}
调试
同时可以打开http://localhost:3000/graphql 进行接口调用。
通过id查询
query {
user(id: 1) {
username,
password
}
}
返回
{
"data": {
"user": {
"username": "root",
"password": "root"
}
}
}
使用用户名查询
query {
userByUsername(username: "root") {
username,
password
}
}
返回
{
"data": {
"userByUsername": {
"username": "root",
"password": "root"
}
}
}
增
直接上代码
@Mutation(returns => User) // 定义为graphql改动
async create(@Args('payload') payload: UserCreateInput): Promise<User> {// 创建入参
return await this.userService.create(payload);
}
依然比较简单,这里把Query改为Mutation,就是把查询操作改为更改操作。
内容体中的语句依然是符合控制器时期的习惯的。
唯一需要注意的是入参,这边定义了一个UserCreateInput去定义入参,并用class-validator
去验证入参,同时用Partial<User>
去表示是User实体的子集
import { Length, MaxLength } from 'class-validator';
import { Field, InputType } from 'type-graphql';
import { User } from './user.entity';
@InputType()
export class UserCreateInput implements Partial<User> {
@Field()
@MaxLength(30)
username: string;
@Field()
@Length(8, 255)
password: string;
}
调试
mutation {
create(payload: { username: "root2", password: "root2" }) {
id,
username,
password
}
}
返回
{
"data": {
"create": {
"id": "2",
"username": "root2",
"password": "root2"
}
}
}
可以看到返回的是新创建的user的数据。
改
直接上代码
@Mutation(returns => User)
async update(@Args('id') id: number, @Args('payload') payload: UserUpdateInput): Promise<User> { // 注意这边新定义了UserUpdateInput,用于定义可更新的值
return await this.userService.update(id, payload);
}
可以看出来其实是类似增的一个写法,也是Mutation,所以这次就不过多讲述了。
不过有些人可能对返回一个User感到不习惯,因为其实并不需要更新的数据,可能只是一个简单的post请求。
那么就可以针对resolve方法进行更改
@Mutation(returns => Boolean) // 更改返回为boolean值
async update(@Args('id') id: number, @Args('payload') payload: UserUpdateInput): Promise<User> {
await this.userService.update(id, payload); // 更新
return true; // 返回true,表示更新完成,假如上述更新出错,那么将会抛出错误,并且不会走到这一行
}
删
@Mutation(returns => Boolean)
async remove(@Args('id') id: number): Promise<boolean> {
await this.userService.remove(id);
return true;
}
删除就可以直接用到之前提到直接返回布尔值的方式。
mutation {
remove(id: 2)
}
返回
{
"data": {
"remove": true
}
}
残留问题
- 能不能通过重载,实现自动识别入参,达到数字时查询id,字符串时查询用户名,对象时通过筛选条件查询用户。
- 我为了约束入参,定义了UserCreateInput和UserUpdateInput,能不能通过User实体的配置解决,否则依然是维护多套代码。这可能是ts的使用范畴,需要学习。
- 我目前就一个User实体,定义了增删改的mutation,但是名字过于通用,无法与后续代码区分。不知道实际项目会怎么处理这个问题。需要查看一些开源项目来学习。
您必须登录才能发表评论。