翻译 |《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
增加性能用于创立和验证用户帐户。
如果有了解不到位的中央,欢送大家纠错。如果感觉还能够,麻烦您点赞珍藏或者分享一下,心愿能够帮到更多人。