apollo安装与使用教程
In my previous article, I explained why it makes sense to decouple the front-end part of a website from its back-end services. I introduced GraphQL, Apollo and other tools that enable such abstraction and make maintenance of production websites a nice experience.
在上一篇文章中 ,我解释了为什么将网站的前端部分与后端服务脱钩是有意义的。 我介绍了GraphQL,Apollo和其他工具,这些工具可以实现这种抽象并使维护生产网站成为一种不错的体验。
In this article, I will show you a boilerplate that already has all these tools set up and saves you a lot of time when starting the development.
在本文中,我将向您展示已经设置了所有这些工具的样板,并在开始开发时为您节省了很多时间。
Check out the live demo of the boilerplate
Let’s start with the tools I used:
让我们从我使用的工具开始:
The first step in building the site is to create or generate a schema. I already mentioned in the previous article that I am using Content-as-a-Service platform Kentico Cloud for content storage. The content that is stored there is already structured within defined model structures. Therefore I can quickly generate the schema using the schema generator:
构建站点的第一步是创建或生成模式。 我已经在上一篇文章中提到过,我正在使用“ 内容即服务”平台Kentico Cloud进行内容存储。 在那里存储的内容已经在定义的模型结构中进行了结构化。 因此,我可以使用模式生成器快速生成模式 :
kc-generate-gql-schema — projectId {projectID} — createModule
kc-generate-gql-schema — projectId {projectID} — createModule
But it’s also possible to define all the models manually in the following syntax.
但是也可以使用以下语法手动定义所有模型。
const TYPE_DEFINITION = ` type SystemInfo { id: String! name: String! codename: String! language: String! type: String! lastModified: String! } interface ContentItem { system: SystemInfo! } ... type FactAboutUsContentType implements ContentItem { system: SystemInfo! description: RichTextElement title: TextElement image: AssetElement } ...`module.exports = { TYPE_DEFINITION}
const TYPE_DEFINITION =`type SystemInfo {id:字符串! 名称:字符串! 代号:字符串! 语言:字符串! 类型:字符串! lastModified:字符串! }接口ContentItem {系统:SystemInfo! } ...类型FactAboutUsContentType实现ContentItem {系统:SystemInfo! 描述:RichTextElement标题:TextElement图片:AssetElement} ...`module.exports = {TYPE_DEFINITION}
(See the whole file on GitHub.)
(请参阅 GitHub上 的整个文件 。)
The model generator lists all the system types including links, texts, datetime fields, images and others (SystemInfo
above), followed by the data models of each of the custom content models (FactAboutUsContentType
). We will need to use the type definition as a module, hence the last argument createModule
.
模型生成器列出所有系统类型,包括链接,文本,日期时间字段,图像和其他(上面的SystemInfo
),然后FactAboutUsContentType
每个自定义内容模型的数据模型( FactAboutUsContentType
)。 我们将需要使用类型定义作为模块,因此需要最后一个参数createModule
。
The next step is to create GraphQL queries and resolvers. As the content API is read-only, the queries are quite simple and limited to fetch all items or items grouped by type:
下一步是创建GraphQL查询和解析器。 由于内容API是只读的,因此查询非常简单,并且仅限于获取所有项或按类型分组的项:
const queryTypes = ` type Query { items: [ContentItem], itemsByType(type: String!, limit: Int, depth: Int, order: String): [ContentItem] }`;
const queryTypes =`type Query {items:[ContentItem],itemsByType(type:String !, limit:Int,depth:Int,order:String):[ContentItem]}`;
(See the whole file on GitHub.)
(请参阅 GitHub上 的整个文件 。)
And right after the definition, we can create a resolver for the headless CMS API:
在定义之后,我们可以为无头CMS API创建解析器:
const deliveryClient = new DeliveryClient(deliveryConfig);const resolvers = { ... Query: { items: async () => { const response = await deliveryClient.items() .getPromise(); return response.items; }, itemsByType: async (_, { type, limit, depth, order }) => { const query = deliveryClient.items() .type(type); limit && query.limitParameter(limit); depth && query.depthParameter(depth); order && query.orderParameter(order); const response = await query.getPromise(); return response.items; } },};
const deliveryClient = new DeliveryClient(deliveryConfig); const解析器= {...查询:{项目:async()=> {const response = await deliveryClient.items().getPromise(); 返回response.items; },itemsByType:异步(_,{类型,限制,深度,顺序})=> {const query = deliveryClient.items().type(type); 限制&& query.limitParameter(limit); depth && query.depthParameter(depth); 订单&& query.orderParameter(order); const response =等待query.getPromise(); 返回response.items; }},};
(See the whole file on GitHub.)
(请参阅 GitHub上 的整个文件 。)
Did you notice that the queries always return generic type ContentItem
even though there are more specific types like FactAboutUsContentType
that inherit ContentItem
defined? If you did, great job! Defining a specific query for every single type would be inefficient (there would be so many of them). Therefore both our queries return ContentItem
data. But how do we ensure the right models are returned at runtime?
您是否注意到查询始终返回通用类型ContentItem
即使还有更具体的类型(如FactAboutUsContentType
继承了定义的ContentItem
? 如果您做到了,那就太好了! 为每种类型定义一个特定的查询效率很低(其中有很多查询)。 因此,我们两个查询都返回ContentItem
数据。 但是,我们如何确保在运行时返回正确的模型?
Every content item that comes from the headless CMS contains information about its type. You can see the string property Type
in the definition of SystemInfo
data model above.
来自无头CMS的每个内容项都包含有关其类型的信息。 您可以在上面的SystemInfo
数据模型的定义中看到字符串属性Type
。
{ "system": { "type": "fact_about_us" ... }...}
{“ system”:{“ type”:“ fact_about_us” ...} ...}
Now we know that the content item is of type fact_about_us
which corresponds to generated data model FactAboutUsContentType
. Therefore we need to translate the type name to pascal case and ensure that GraphQL uses the right data model. We can ensure this using a special resolver for the generic data model:
现在我们知道内容项的类型为fact_about_us
,它对应于生成的数据模型FactAboutUsContentType
。 因此,我们需要将类型名称转换为pascal大小写,并确保GraphQL使用正确的数据模型。 我们可以使用通用数据模型的特殊解析器来确保这一点:
...const resolvers = { ContentItem: { __resolveType(item, _context, _info) { // fact_about_us -> FactAboutUs const type = convertSnakeCaseToPascalCase(item); // FactAboutUs -> FactAboutUsContentType return type + 'ContentType'; }},...
... const解析器= {ContentItem:{__resolveType(item,_context,_info){// fact_about_us-> FactAboutUs const type = convertSnakeCaseToPascalCase(item); // FactAboutUs-> FactAboutUsContentType返回类型+'ContentType'; }},...
(See the whole file on GitHub.)
(请参阅 GitHub上 的整个文件 。)
And add a simple function to translate the type name to the data model name:
并添加一个简单的函数将类型名称转换为数据模型名称:
...// fact_about_us -> FactAboutUsconst convertSnakeCaseToPascalCase = (item) => { return item.system.type .split('_') .map((str) => str.slice(0, 1).toUpperCase() + str.slice(1, str.length)) .join(''); }...
... // fact_about_us-> FactAboutUsconst convertSnakeCaseToPascalCase =(item)=> {返回item.system.type .split('_').map((str)=> str.slice(0,1).toUpperCase() + str.slice(1,str.length)).join(''); } ...
(See the whole file on GitHub.)
(请参阅 GitHub上 的整个文件 。)
You see that for the implementation of the resolver you need to know the target service API, or in this case the specifics of the SDK. The developer working on the front-end only needs to know the GraphQL schema regardless of the services you use.
您会看到,对于解析器的实现,您需要了解目标服务API,或者在这种情况下需要了解SDK的详细信息。 不管您使用什么服务,从事前端工作的开发人员只需要知道GraphQL模式。
To bring our data models, queries and resolvers to life, we need to create the Apollo server instance in the main app.js
file and connect it with Express and our GraphQL schema definitions:
为了使我们的数据模型,查询和解析器栩栩如生,我们需要在主app.js
文件中创建Apollo服务器实例,并将其与Express和GraphQL模式定义连接:
const { TYPE_DEFINITION } = require('./graphQL/types');const { queryTypes, resolvers } = require('./graphQL/queries');const app = express();const apolloServer = new ApolloServer({ introspection: true, playground: true, typeDefs: [ TYPE_DEFINITION, queryTypes ], resolvers});apolloServer.applyMiddleware({ app, path: graphQLPath});
const {TYPE_DEFINITION} = require('./ graphQL / types'); const {queryTypes,解析器} = require('./ graphQL / queries'); const app = express(); const apolloServer =新的ApolloServer({内省: true,游乐场:true,typeDefs:[TYPE_DEFINITION,queryTypes],resolvers}); apolloServer.applyMiddleware({app,path:graphQLPath});
(See the whole file on GitHub.)
(请参阅 GitHub上 的整个文件 。)
In this code, we are telling Apollo which schema to use. The definitions are provided in the typeDefs
array and correspond to previously created queries and resolvers.
在这段代码中,我们告诉Apollo使用哪种模式。 这些定义在typeDefs
数组中提供,并且对应于先前创建的查询和解析器。
The rest of the code in app.js
(omitted here, but you may take a look at the whole file on GitHub) is related to Pug templating and routing engine. Pug enables building pages and routes in MVC structure, so it’s easy and straightforward. Take a look at the routes/index.js
file (file on GitHub) that contains the definition of the only route in the boilerplate project:
app.js
的其余代码(此处省略,但是您可以查看GitHub上的整个文件 )与Pug模板和路由引擎有关。 Pug支持以MVC结构构建页面和路由,因此非常简单明了。 看一下routes/index.js
文件( 在GitHub上的文件 ),该文件包含样板项目中唯一路由的定义:
...router.get('/', async function (_req, res, _next) { const result = await apolloClient.query({ query: gql` { itemsByType(type: "article", limit: 3, depth: 0, order: "elements.post_date") { ... on ArticleContentType { title { value } summary { value } teaser_image { assets { name url } } } } }` }); res.render('index', { articles: result.data.itemsByType, ... });});module.exports = router;
... router.get('/',异步函数(_req,res,_next){const result = await apolloClient.query({query:gql` {itemsByType(type:“ article”,limit:3,depth:0 ,顺序:“ elements.post_date”){... on ArticleContentType {标题{值}摘要{值} teaser_image {资产{名称url}}}}}})}); res.render('index',{文章: result.data.itemsByType,...});}); module.exports =路由器;
Yes! Finally, a GraphQL query. You see it requests all articles ordered by post_date
and specifies which data fields should be provided in the response (title
, summary
, teaser_image
).
是! 最后是GraphQL查询。 您会看到它请求按post_date
排序的所有文章,并指定响应中应提供哪些数据字段( title
, summary
, teaser_image
)。
Note here that in the query we need to specify which data model we are expecting because not all children of ContentItem
must contain requested fields (for example summary
or teaser_image
). By … on ArticleContentType
we are basically creating a switch
case that will return defined fields (title
, summary
and teaser_image
) if the returned content item is of type ArticleContentType
.
请注意,在此查询中,我们需要指定期望的数据模型,因为并非ContentItem
所有子级都必须包含请求的字段(例如summary
或teaser_image
)。 通过… on ArticleContentType
我们基本上建立一个switch
的情况下,将返回定义的字段( title
, summary
和teaser_image
)如果返回的内容产品类型的ArticleContentType
。
The Apollo Client sends this request to the Apollo Server which forwards it to the Kentico Cloud resolver. The resolver translates the GraphQL query into the REST API. The content takes the same way back to Pug which renders the page according to template in views/index.pug
.
Apollo客户端将此请求发送到Apollo服务器,该服务器将其转发到Kentico Cloud解析器。 解析器将GraphQL查询转换为REST API。 内容以相同的方式返回到Pug,后者根据views/index.pug
模板呈现页面。
How does it all work together? Take a look at the live demo.
它们如何一起工作? 看一下现场演示 。
All the tools I’ve used and shown you are easy to put together, but why reinvent the wheel? When you want to start implementing a website using Apollo and React or any other JavaScript framework, remember this boilerplate to save yourself some time and effort. If you find anything missing or wish to enhance it, feel free to raise an issue or add it directly to the code base.
我使用过的所有工具都可以轻松组合在一起,但是为什么要重新发明轮子呢? 当您要开始使用Apollo和React或任何其他JavaScript框架实现网站时,请记住此样板,以节省一些时间和精力。 如果您发现任何缺失或希望对其进行增强,请随时提出问题或将其直接添加到代码库中。
Do you have experience using Apollo and GraphQL to separate concerns? Would you recommend it to others? Let me know in comments.
您是否有使用Apollo和GraphQL分离问题的经验? 你会推荐给其他人吗? 在评论中让我知道。
翻译自: https://www.freecodecamp.org/news/how-to-use-graphql-with-apollo-on-your-website-ecb6046e139/
apollo安装与使用教程