GraphQL 是一种用于 API 的查询语言,它允许客户端精确地指定需要的数据,从而减少了传输过多或不必要的数据的情况。Next.js 是一款基于 React 的轻量级框架,它提供了很多工具和功能,可以帮助我们快速开发 React 应用。Apollo 是一款流行的 GraphQL 客户端,它提供了很多工具和功能,可以帮助我们轻松地在应用程序中使用 GraphQL。
在本文中,我们将介绍如何使用 Next.js 和 Apollo 来构建一个 GraphQL 应用程序。我们将从安装和设置开始,一步步地实现一个完整的 GraphQL 应用程序,包括如何定义和查询数据模型、如何处理用户输入和错误、以及如何部署应用程序。
环境设置
在开始之前,我们需要安装一些必要的工具和依赖项。
首先,我们需要安装 Node.js 和 npm。这里我们推荐使用 nvm 来安装和管理 Node.js 版本。可以使用以下命令来安装 nvm:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
安装完成后,可以使用以下命令来安装最新的 Node.js 版本:
nvm install node
接下来,我们需要安装 Next.js 和 Apollo。
使用以下命令来安装 Next.js:
npm install next react react-dom
使用以下命令来安装 Apollo:
npm install @apollo/client graphql
数据模型定义
在开始构建 GraphQL 应用程序之前,我们需要定义我们的数据模型。在本文中,我们将使用一个简单的博客应用程序作为示例。我们的博客应用程序将包含以下数据模型:
- Post:文章,包含标题、内容和发布日期
- User:用户,包含用户名和电子邮件地址
- Comment:评论,包含评论内容和评论日期
我们可以使用 GraphQL 的类型定义语言来定义我们的数据模型。以下是我们的数据模型的类型定义:
// javascriptcn.com 代码示例 type Post { id: ID! title: String! content: String! publishedAt: String! author: User! comments: [Comment!]! } type User { id: ID! username: String! email: String! posts: [Post!]! } type Comment { id: ID! content: String! publishedAt: String! author: User! post: Post! } type Query { post(id: ID!): Post posts: [Post!]! user(id: ID!): User users: [User!]! comment(id: ID!): Comment comments: [Comment!]! } type Mutation { createPost(title: String!, content: String!, authorId: ID!): Post! updatePost(id: ID!, title: String, content: String): Post! deletePost(id: ID!): Post! createUser(username: String!, email: String!): User! updateUser(id: ID!, username: String, email: String): User! deleteUser(id: ID!): User! createComment(content: String!, authorId: ID!, postId: ID!): Comment! updateComment(id: ID!, content: String): Comment! deleteComment(id: ID!): Comment! }
在上面的类型定义中,我们定义了三个类型:Post、User 和 Comment。每个类型都有一些字段,例如 Post 类型包含 id、title、content、publishedAt、author 和 comments 字段。我们还定义了一个 Query 类型和一个 Mutation 类型,它们分别用于查询和修改数据。例如,我们可以使用 Query 类型中的 post 和 posts 字段来查询文章,使用 Mutation 类型中的 createPost、updatePost 和 deletePost 字段来创建、更新和删除文章。
数据源设置
在定义了我们的数据模型之后,我们需要将它们存储在某个数据源中。在本文中,我们将使用 MongoDB 数据库作为我们的数据源。
首先,我们需要安装 MongoDB。可以在 MongoDB 的官方网站上下载和安装 MongoDB:https://www.mongodb.com/try/download/community
安装完成后,我们需要创建一个数据库和集合来存储我们的数据。可以使用以下命令来启动 MongoDB:
mongod
接下来,我们可以使用以下命令来连接 MongoDB 并创建我们的数据库和集合:
mongo use blog db.createCollection("posts") db.createCollection("users") db.createCollection("comments")
现在我们已经准备好将我们的数据存储在 MongoDB 中了。
GraphQL API 实现
现在我们已经定义了我们的数据模型并准备好将数据存储在 MongoDB 中,接下来我们需要构建我们的 GraphQL API。
在本文中,我们将使用 Next.js 来构建我们的 GraphQL API。Next.js 提供了一个 api 路由,我们可以使用它来构建我们的 GraphQL API。
首先,我们需要创建一个 pages/api/graphql.js 文件来定义我们的 GraphQL API。在这个文件中,我们需要导入 ApolloServer,并在服务器上设置 GraphQL API。
以下是我们的 pages/api/graphql.js 文件的代码:
// javascriptcn.com 代码示例 import { ApolloServer, gql } from "@apollo/client"; import { MongoClient } from "mongodb"; const typeDefs = gql` type Post { id: ID! title: String! content: String! publishedAt: String! author: User! comments: [Comment!]! } type User { id: ID! username: String! email: String! posts: [Post!]! } type Comment { id: ID! content: String! publishedAt: String! author: User! post: Post! } type Query { post(id: ID!): Post posts: [Post!]! user(id: ID!): User users: [User!]! comment(id: ID!): Comment comments: [Comment!]! } type Mutation { createPost(title: String!, content: String!, authorId: ID!): Post! updatePost(id: ID!, title: String, content: String): Post! deletePost(id: ID!): Post! createUser(username: String!, email: String!): User! updateUser(id: ID!, username: String, email: String): User! deleteUser(id: ID!): User! createComment(content: String!, authorId: ID!, postId: ID!): Comment! updateComment(id: ID!, content: String): Comment! deleteComment(id: ID!): Comment! } `; const resolvers = { Query: { post: async (_, { id }, { db }) => await db.collection("posts").findOne({ _id: id }), posts: async (_, __, { db }) => await db.collection("posts").find().toArray(), user: async (_, { id }, { db }) => await db.collection("users").findOne({ _id: id }), users: async (_, __, { db }) => await db.collection("users").find().toArray(), comment: async (_, { id }, { db }) => await db.collection("comments").findOne({ _id: id }), comments: async (_, __, { db }) => await db.collection("comments").find().toArray(), }, Mutation: { createPost: async (_, { title, content, authorId }, { db }) => { const post = { title, content, publishedAt: new Date().toISOString(), authorId }; const result = await db.collection("posts").insertOne(post); return await db.collection("posts").findOne({ _id: result.insertedId }); }, updatePost: async (_, { id, title, content }, { db }) => { const result = await db.collection("posts").findOneAndUpdate( { _id: id }, { $set: { title, content } }, { returnOriginal: false } ); return result.value; }, deletePost: async (_, { id }, { db }) => { const result = await db.collection("posts").findOneAndDelete({ _id: id }); return result.value; }, createUser: async (_, { username, email }, { db }) => { const user = { username, email }; const result = await db.collection("users").insertOne(user); return await db.collection("users").findOne({ _id: result.insertedId }); }, updateUser: async (_, { id, username, email }, { db }) => { const result = await db.collection("users").findOneAndUpdate( { _id: id }, { $set: { username, email } }, { returnOriginal: false } ); return result.value; }, deleteUser: async (_, { id }, { db }) => { const result = await db.collection("users").findOneAndDelete({ _id: id }); return result.value; }, createComment: async (_, { content, authorId, postId }, { db }) => { const comment = { content, publishedAt: new Date().toISOString(), authorId, postId }; const result = await db.collection("comments").insertOne(comment); return await db.collection("comments").findOne({ _id: result.insertedId }); }, updateComment: async (_, { id, content }, { db }) => { const result = await db.collection("comments").findOneAndUpdate( { _id: id }, { $set: { content } }, { returnOriginal: false } ); return result.value; }, deleteComment: async (_, { id }, { db }) => { const result = await db.collection("comments").findOneAndDelete({ _id: id }); return result.value; }, }, Post: { author: async ({ authorId }, _, { db }) => await db.collection("users").findOne({ _id: authorId }), comments: async ({ _id }, _, { db }) => await db.collection("comments").find({ postId: _id }).toArray(), }, User: { posts: async ({ _id }, _, { db }) => await db.collection("posts").find({ authorId: _id }).toArray(), }, Comment: { author: async ({ authorId }, _, { db }) => await db.collection("users").findOne({ _id: authorId }), post: async ({ postId }, _, { db }) => await db.collection("posts").findOne({ _id: postId }), }, }; const apolloServer = new ApolloServer({ typeDefs, resolvers, context: async () => { const client = await MongoClient.connect(process.env.MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true, }); const db = client.db(); return { db }; }, }); export default apolloServer.createHandler({ path: "/api/graphql" });
在上面的代码中,我们首先定义了我们的类型定义和解析器。然后,我们创建了一个 apolloServer 实例,并将类型定义和解析器传递给它。在我们的 apolloServer 实例中,我们设置了一个 context 函数,它将返回一个包含数据库连接的对象。最后,我们使用 apolloServer.createHandler 函数创建了一个 Next.js api 路由,并将其导出。
现在我们已经创建了我们的 GraphQL API,可以使用我们的 API 来查询和修改我们的数据了。
使用 GraphQL API
现在我们已经创建了我们的 GraphQL API,接下来我们需要使用它来查询和修改我们的数据。
首先,我们需要创建一个 pages/index.js 文件来定义我们的 React 应用程序。在这个文件中,我们需要导入 ApolloProvider,并在客户端上设置 Apollo 客户端。
以下是我们的 pages/index.js 文件的代码:
// javascriptcn.com 代码示例 import { ApolloProvider, ApolloClient, InMemoryCache, gql } from "@apollo/client"; const client = new ApolloClient({ uri: "/api/graphql", cache: new InMemoryCache(), }); export default function Home() { return ( <ApolloProvider client={client}> <div>Hello World!</div> </ApolloProvider> ); }
在上面的代码中,我们创建了一个 Apollo 客户端,并将其传递给 ApolloProvider。我们将 Apollo 客户端的 URI 设置为我们的 GraphQL API 的路径。
现在我们已经设置好了 Apollo 客户端,接下来我们需要使用它来查询和修改我们的数据。
以下是使用 Apollo 客户端查询所有文章的示例代码:
// javascriptcn.com 代码示例 import { useQuery, gql } from "@apollo/client"; const GET_POSTS = gql` query { posts { id title content publishedAt } } `; export default function Home() { const { loading, error, data } = useQuery(GET_POSTS); if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>; return ( <div> {data.posts.map((post) => ( <div key={post.id}> <h2>{post.title}</h2> <p>{post.content}</p> <p>{post.publishedAt}</p> </div> ))} </div> ); }
在上面的代码中,我们定义了一个 GET_POSTS 查询,该查询将返回所有文章的 id、title、content 和 publishedAt。然后,我们使用 useQuery 钩子来执行 GET_POSTS 查询,并根据加载状态和错误状态呈现不同的内容。最后,我们将返回的数据呈现为一个列表。
以下是使用 Apollo 客户端创建一篇文章的示例代码:
// javascriptcn.com 代码示例 import { useMutation, gql } from "@apollo/client"; const CREATE_POST = gql` mutation CreatePost($title: String!, $content: String!, $authorId: ID!) { createPost(title: $title, content: $content, authorId: $authorId) { id title content publishedAt } } `; export default function Home() { const [createPost] = useMutation(CREATE_POST); const handleCreatePost = async () => { const result = await createPost({ variables: { title: "My First Post", content: "Hello World!", authorId: "123", }, }); console.log(result.data.createPost); }; return ( <div> <button onClick={handleCreatePost}>Create Post</button> </div> ); }
在上面的代码中,我们定义了一个 CREATE_POST 变异,该变异将创建一篇文章,并返回创建的文章的 id、title、content 和 publishedAt。然后,我们使用 useMutation 钩子来执行 CREATE_POST 变异,并在点击按钮时调用 handleCreatePost 函数。在 handleCreatePost 函数中,我们使用 createPost 函数来创建一篇文章,并将其打印到控制台中。
现在我们已经学习了如何使用 Apollo 客户端来查询和修改我们的数据。我们可以使用类似的方式来查询和修改我们的用户和评论数据。
部署应用程序
现在我们已经构建了一个完整的 GraphQL 应用程序,接下来我们需要将其部署到生产环境中。
在本文中,我们将使用 Vercel 来部署我们的 Next.js 应用程序。Vercel 是一个免费的托管平台,它可以帮助我们快速部署和扩展我们的应用程序。
首先,我们需要将我们的代码推送到 GitHub 或 GitLab 上。然后,我们可以使用 Vercel 的自动部署功能来部署我们的应用程序。
在 Vercel 上,我们需要创建一个新项目,并将我们的代码库与该项目关联。然后,我们可以使用 Vercel 的自动部署功能来部署我们的应用程序。
现在我们已经成功部署了我们的应用程序,可以使用它来查询和修改我们的数据了。
总结
在本文中,我们学习了如何使用 Next.js 和 Apollo 来构建一个完整的 GraphQL 应用程序。我们首先定义了我们的数据模型,然后将其存储在 MongoDB 中。然后,我们使用 Next.js 创建了我们的 GraphQL API,并使用 Apollo 客户端来查询和修改我们的数据。最后,我们将我们的应用程序部署到 Vercel 上。
GraphQL 是一个强大的 API 查询语言,它可以帮助我们更有效地查询和修改我们的数据。Next.js 和 Apollo 是两个强大的工具,它们可以帮助我们更快地开发和部署我们的应用程序。使用这些工具和技术,我们可以构建出更好的 Web 应用程序。
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/6579846fd2f5e1655d38e141