翻译 | 《JavaScript Everywhere》第6章 CRUD操作(^\_^)
写在最前面
大家好呀,我是毛小悠,是一位前端开发工程师。正在翻译一本英文技术书籍。
为了提高大家的阅读体验,对语句的结构和内容略有调整。如果发现本文中有存在瑕疵的地方,或者你有任何意见或者建议,可以在评论区留言,或者加我的微信:code\_maomao,欢迎相互沟通交流学习。
(σ゚∀゚)σ..:\*☆哎哟不错哦
第6章 CRUD操作
第一次听到“ CRUD
应用程序”一词时,我错误地认为它指的是用来执行一些肮脏或棘手的应用程序。诚然,“ CRUD
”听起来好像是指从鞋底刮下来的东西。实际上,首字母缩写词是在1980
年代初期由英国技术作家James Martin
引用的,用于创建、读取、更新和删除数据的应用程序。尽管这个术语已经存在了25
年之久,但它仍然适用于当今开发的许多应用程序。考虑一下你每天与之交互的应用程序,例如待办事项列表,电子表格,内容管理系统,文本编辑器,社交媒体网站,用于及其他几个应用程序,很可能其中许多应用程序都属于CRUD
应用程序格式。用户可用于创建一些数据,访问或读取数据,并可用于更新或删除该数据。
我们的Noted
应用程序将遵循CRUD
模式。用户将能够创建、阅读、更新和删除自己的笔记。在本章中,我们将通过连接解析器和数据库来实现API
的基本CRUD
功能。
分离我们的GraphQL模式和解析器
当前,我们的src/index.js
文件是Express/Apollo
服务器代码用于及API
的结构和解析器的所在地。
可用于想象,随着我们代码库的增长,这可能会变得有些笨拙。在此之前,让我们花一些时间进行较小的重构,用于将我们的结构、解析器和服务器代码分开。首先,我们将在src
文件夹中创建一个名为src/schema.js
的新文件,然后将在typeDefs
变量中找到的结构内容移动到该文件中。为此,我们还需要导入apollo-server-express
软件包随附的gql
模式语言,并使用Node
将我们的模式导出为module.exports
方式。
首先,让我们将GraphQL
模式移动到它自己的文件中。
在此过程中,我们还可用于删除hello
查询,这在最终应用程序中将不再被需要:
const { gql } = require('apollo-server-express');
module.exports = gql` type Note { id: ID!
content: String!
author: String!
}
type Query { notes: [Note!]!
note(id: ID!): Note!
}
type Mutation {
newNote(content: String!): Note!
} `;
我们现在通过导入这个外部结构文件,可用于更新src/index.js
文件,并从apollo server express
中删除导入的gql
,如下所示:
const { ApolloServer } = require('apollo-server-express');
const typeDefs = require('./schema');
现在我们已经将GraphQL
模块隔离到了自己的文件中,让我们为GraphQL
解析器代码做类似的事情。我们的解析器代码将包含我们绝大多数API
逻辑,因此首先我们将创建一个文件夹来存储此代码,称为解析器。在src/resolvers
目录中,我们将从三个文件开始:src/resolvers/index.js
,src/resolvers/query.js
和src/resolvers/mutation.js
。与我们在数据库模块中遵循的模式相似,src/resolvers/index.js
文件将用于将解析器代码导入到单个导出模块中。继续设置该文件,如下所示:
const Query = require('./query');
const Mutation = require('./mutation');
module.exports = {
Query,
Mutation
};
现在,你可用于为API
查询代码设置src/resolvers/query.js
:
module.exports = {
notes: async () => {
return await models.Note.find()
},
note: async (parent, args) => {
return await models.Note.findById(args.id);
}
}
然后将修改代码移至src/resolvers/mutation.js
文件:
module.exports = {
newNote: async (parent, args) => {
return await models.Note.create({
content: args.content,
author: 'Adam Scott'
});
}
}
接下来,服务器通过将用于下行添加到src/index.js
文件来导入解析器代码:
const resolvers = require('./resolvers');
重构解析器的最后一步是将它们连接到我们的数据库模块。你可能已经注意到,我们的解析器模块引用了这些模块,但是无法访问它们。要解决此问题,我们将使用Apollo Server
中调用的概念context
,它使我们可用于根据每个请求将特定信息从服务器代码传递到单个解析器中。目前,这可能有点过头,但是对于将用户身份验证合并到我们的应用程序中将很有用。为此,我们将更新位于str/index.js
的Apollo Server
启动代码,通过一个返回我们的数据库模块的上下文函数。
// Apollo Server setup
const server = new ApolloServer({
typeDefs,
resolvers,
context: () => {
// Add the db models to the context
return { models };
}
});
现在,我们通过将{modules
}添加为每个函数的第三个参数,然后更新每个解析器来利用此上下文函数。
在src/resolvers/query.js
中执行用于下操作:
module.exports = {
notes: async (parent, args, { models }) => {
return await models.Note.find()
},
note: async (parent, args, { models }) => {
return await models.Note.findById(args.id);
}
}
将修改代码移到src/resolvers/mutation.js
文件中:
module.exports = {
newNote: async (parent, args, { models }) => {
return await models.Note.create({
content: args.content,
author: 'Adam Scott'
});
}
}
现在,我们的src/index.js
文件将简化如下:
const express = require('express');
const { ApolloServer } = require('apollo-server-express');
require('dotenv').config();
// Local module imports
const db = require('./db');
const models = require('./models');
const typeDefs = require('./schema');
const resolvers = require('./resolvers');
// Run our server on a port specified in our .env file or port 4000
const port = process.env.PORT || 4000;
const DB_HOST = process.env.DB_HOST;
const app = express();
db.connect(DB_HOST);
// Apollo Server setup
const server = new ApolloServer({
typeDefs,
resolvers,
context: () => {
// Add the db models to the context
return { models };
}
});
// Apply the Apollo GraphQL middleware and set the path to /api
server.applyMiddleware({ app, path: '/api' });
app.listen({ port }, () =>
console.log(
`GraphQL Server running at http://localhost:${port}${server.graphqlPath}`
)
);
编写我们的GraphQL CRUD模式
既然我们已经重构了代码提高了灵活性,那么让我们开始实现CRUD
操作。我们已经能够创建和阅读笔记,这使我们能够实现更新和删除功能。首先,我们要更新结构。
由于更新和删除操作将更改我们的数据,因此它们将会被修改。我们更新笔记将需要一个ID
参数来定位笔记用于及新笔记的内容。然后,更新查询将返回新更新的笔记。对于我们的删除操作,我们的API
将返回布尔值true
,用于通知我们笔记删除成功。
更新src/schema.js
中的修改结构如下:
type Mutation {
newNote(content: String!): Note!
updateNote(id: ID!, content: String!): Note!
deleteNote(id: ID!): Boolean!
}
有了这些附加功能,我们的结构现在可用于执行CRUD
操作了。
CRUD解析器
有了我们的结构后,现在可以更新解析器来删除或更新笔记。让我们修改我们的deleteNote
。要删除笔记,我们将使用Mongoose
的findOneAndRemove
方法,并将要删除的商品的ID
传递给它。如果找到并删除了我们的商品,我们将向客户端返回true
,但是如果我们无法删除商品,则将返回false
。
在src/resolvers/mutation.js
中,在模块内添加module.exports
对象:
deleteNote: async (parent, { id }, { models }) => {
try {
await models.Note.findOneAndRemove({ _id: id});
return true;
} catch (err) {
return false;
}
},
现在我们可以在GraphQL Playground
中运行我们的修改。在Playground
的新标签中,输入以下修改,并确保使用数据库中笔记之一中的ID
:
mutation {
deleteNote(id: "5c7d1aacd960e03928804308")
}
如果笔记被成功删除,你应该收到true
的响应:
{
"data": {
"deleteNote": true
}
}
如果传递不存在的ID
,则会收到“ deleteNote
”响应:false
。
有了我们的删除功能后,让我们编写我们的
updateNote
修改。为此,我们将使用Mongoose
的
findOneAndUpdate
方法。该方法将使用查询的初始参数在数据库中找到正确的笔记,然后是第二个参数,在此我们将设置新笔记的内容。最后,我们将传递第三个参数new
:true
,它指示数据库将更新后的笔记内容返回给我们。
在src/resolvers/mutation.js
中,在模块中添加module.exports
updateNote: async (parent, { content, id }, { models }) => {
return await models.Note.findOneAndUpdate(
{
_id: id,
},
{
$set: {
content
}
},
{
new: true
}
);
},
现在,我们可用于在浏览器中访问GraphQL Playground
来尝试我们的updateNote
修改。在Playground
上的新标签页中,使用id
和content
参数编写一个修改:
mutation {
updateNote(
id: "5c7d1f0a31191c4413edba9d",
content: "This is an updated note!"
){
id
content
}
}
如果我们的修改按预期工作,则GraphQL
响应应如下所示:
{
"data": {
"updateNote": {
"id": "5c7d1f0a31191c4413edba9d",
"content": "This is an updated note!"
}
}
}
如果我们传递了错误的ID
,则响应将失败,并且我们将收到内部服务器错误,并带有错误更新笔记的消息。
现在,我们可用于创建、阅读、更新和删除笔记。这样,我们的API
中就具有完整的CRUD
功能。
日期和时间
创建数据库结构时,我们要求Mongoose
自动存储时间戳用于记录在数据库中创建和更新条目的时间。此信息在我们的应用程序中将很有用,因为它使我们可用于在用户界面中向用户显示笔记的创建时间或最后编辑时间。让我们添加createdAt
和updatedAt
字段来存放这些值。
你可能还记得GraphQL
允许使用默认类型的String
,Boolean
,Int
,Float
和ID
。不幸的是,GraphQL
没有内置的日期变量类型。我们可以使用String
类型,但是这意味着我们将不能利用GraphQL
提供的类型验证,从而确保我们输入的日期和时间内容一定是日期和时间。相反,我们可以创建一个自定义的变量类型。
自定义类型允许我们定义一个新类型,并针对请求该类型数据的每个查询和修改进行验证。
让我们通过在GQL
字符串文字的顶部添加一个自定义变量来更新src/schema.js
中的GraphQL
模式:
module.exports = gql` scalar DateTime
... `;
现在,添加createdAt
和updatedAt
字段:
type Note {
id: ID!
content: String!
author: String!
createdAt: DateTime!
updatedAt: DateTime!
}
最后一步是验证此新类型。
虽然我们可以编写自己的验证,但当前对于我们的用例,我们将使用
graphql-iso-date
软件包。现在,我们将向任何请求类型为DateTime
的值的解析器函数添加验证。
在src/resolvers/index.js
文件中,导入包,然后将DateTime
值添加到导出的解析器,如下所示:
const Query = require('./query');
const Mutation = require('./mutation');
const { GraphQLDateTime } = require('graphql-iso-date');
module.exports = {
Query,
Mutation,
DateTime: GraphQLDateTime
};
现在,如果我们在浏览器中访问GraphQL Playground
并刷新页面,则可以验证我们的自定义类型是否按预期工作。如果请求我们的字段,我们可以看到createdAt
和updatedAt
字段是有格式的日期时间。如图6-1
所示,此类型的文档指出它是“ UTC
的日期时间字符串”。
图6-1
。我们的结构现在具有DateTime
类型
为了测试这一点,让我们在GraphQL Playground
中编写一个newNote
修改,其中包括我们的日期字段:
mutation {
newNote (content: "This is a note with a custom type!") {
content
author
id
createdAt
updatedAt
}
}
这将返回createdAt
和updatedAt
数值为ISO
格式的日期。如果我们再运行一个updateNote
对同一个字符修改,我们将看到一个和createdAt
日期不同的updatedAt
值。
有关定义和验证自定义变量类型的更多信息,建议阅读Apollo Server
的“自定义变量和枚举”文档。
结论
在本章中,我们向API
添加了创建、读取、更新和删除(CRUD
)功能。CRUD
是许多应用程序使用的一种非常常见的模式。我希望你查看你每天使用的应用程序,并考虑它们的数据如何使用这种模式。在下一章中,我们将向API
添加功能用于创建和验证用户帐户。
如果有理解不到位的地方,欢迎大家纠错。如果觉得还可以,麻烦您点赞收藏或者分享一下,希望可以帮到更多人。