NodeJS 了解apollo-graphql订阅

4jb9z9bj  于 2022-12-18  发布在  Node.js
关注(0)|答案(2)|浏览(155)

我目前正在学习Graphql(apollo),并在后端使用Node.js。
我在查询和转换方面取得了一些进展,但我在订阅方面停滞不前,我看了一些视频,读了一些博客,但我很难理解,因为他们使用的是前端框架,比如react,而我对react或其他前端javascript框架并不熟悉。
我只对学习它的后端感兴趣。
有人能帮我吗?
我有三个独立的查询(或者不管他们叫什么),我正在工作。
现在我想在有人添加新评论或创建新帖子时生成订阅。对于用户、评论和帖子,我已经有了添加、更新和删除的变体。
目前没有身份验证或授权。

zhte4eai

zhte4eai1#

你的问题更多的是帮助我实现这一点,而不是实际上问一个问题或问题,你所面临的一些步骤后,在一系列的顺序。
这可能有助于您学习和实现GraphQL订阅;

  1. https://www.apollographql.com/docs/apollo-server/data/subscriptions/
  2. https://www.youtube.com/watch?v=R2IrBI4-7Kg&t=698s
    如果你已经尝试了一些东西,并面临着一堵墙,请添加您的代码和你做了什么,所以我可以帮助你在哪里你错了,或者你可以做什么希望。干杯。
q8l4jmvw

q8l4jmvw2#

我提供的解决方案是在@apollo/server v.4上构建的,后端是expressMiddleware and mongodb/mongoose,客户端是subscribeToMoreupdateQuery,而不是useSubscription钩子。
不再支持graphql-transport-ws传输库;请改为使用graphql-ws
该实现区分了三个主要集合:UserPostComment,以及针对post creationpost modification和用户authentication订阅
同样,自2022年12月起,以下设置和配置适用。
后端订阅:
安装以下依赖项:

$ npm i @apollo/server @graphql-tools/schema graphql-subscriptions graphql-ws ws cors body-parser mongoose graphql express

我假设您已经配置了MongoDB模型;如果没有,您可能需要查看一下repo for a basic setup
设置架构类型和解析器,如下面所示。

// typeDefs.js

const typeDefs = `#graphql
  type User {
    id: ID!
    email: String!
    posts: [Post]!
    commentsMade: [Comment]!
  }
  type Token {
    value: String!
  }
  input UpdatePostInput {
    title: String!
  }
  type Post {
    id: ID!
    title: String!
    postedBy: User
    comments: [Comment]!
  }
  input CommentInput {
    text: String!
  }
  type Comment {
    id: ID!
    text: String!
    commenter: User!
    commentFor: Post!
  }
  type Query {
    users: [User]!
    user(id: ID!): User!
    posts: [Post!]!
    post(id: ID!): Post!
    comment(id: ID!): Comment!
    comments: [Comment!]!
  }
  type Mutation {
    signup(email: String!, password: String!): User
    signin(email: String!, password: String!): Token
    createPost(title: String): Post
    createComment(postId: String!, commentInput: CommentInput!): Comment
    updatePost(postId: ID!, updatePostInput: UpdatePostInput!): Post
  }
  type Subscription {
    postAdded: Post
    commentAdded: Comment
    postUpdated: Post
  }
`

export default typeDefs
  
// resolvers.js

import dotenv from 'dotenv'
import { PubSub } from 'graphql-subscriptions'
import mongoose from 'mongoose'
import { GraphQLError } from 'graphql'
import bcrypt from 'bcrypt'
import UserModel from '../models/User.js'
import PostModel from '../models/Post.js'
import CommentModel from '../models/Comment.js'
dotenv.config()

...

const pubsub = new PubSub()
const User = UserModel
const Post = PostModel
const Comment = CommentModel
const secret = process.env.TOKEN_SECRET
const resolvers = {
  Query: {...},
  Mutation: {
    ...
    
    createPost: async (_, args, contextValue) => {
      const authUser = contextValue.authUser
      if (!authUser) {
        throw new GraphQLError('User is not authenticated', {
          extensions: {
            code: 'UNAUTHENTICATED',
            http: { status: 401 },
          },
        })
      }
      const post = new Post({
        ...args,
        postedBy: mongoose.Types.ObjectId(authUser.id),
      })
      try {
        const savedPost = await post.save()
        authUser.posts = authUser.posts.concat(post._id)
        await authUser.save()

        const addedPost = {
          id: savedPost.id,
          title: savedPost.title,
          postedBy: savedPost.postedBy,
          comments: savedPost.comments,
        }
        // subscription postAdded with object iterator POST_ADDED
        pubsub.publish('POST_ADDED', { postAdded: addedPost })

        return post
      } catch (error) {
        throw new GraphQLError(`Error: ${error.message}`, {
          extensions: {
            code: 'BAD_USER_INPUT',
            http: { status: 400 },
            argumentName: args,
          },
        })
      }
    },
    updatePost: async (_, args, contextValue) => {
      const authUser = contextValue.authUser
      if (!authUser) {
        throw new GraphQLError('User is not authenticated', {
          extensions: {
            code: 'UNAUTHENTICATED',
            http: { status: 401 },
          },
        })
      }
      try {
        const post = await Post.findByIdAndUpdate(
          args.postId,
          args.updatePostInput,
          { new: true }
        )
          .populate('comments')
          .populate('postedBy')

        const updatedPost = {
          id: post.id,
          title: post.title,
          postedBy: post.postedBy,
          comments: post.comments,
        }
        // subscription postUpdated with object iterator POST_UPDATED
        pubsub.publish('POST_UPDATED', { postUpdated: updatedPost })
        return post
      } catch (error) {
        throw new GraphQLError(`Error: ${error.message}`, {
          extensions: {
            code: 'BAD_REQUEST',
            http: { status: 400 },
            argumentName: args,
          },
        })
      }
    },
  },
  // resolvers for post addition and post modification using subscribe function
  Subscription: {
    postAdded: {
      subscribe: () => pubsub.asyncIterator('POST_ADDED'),
    },
    commentAdded: {
      subscribe: () => pubsub.asyncIterator('COMMENT_ADDED'),
    },
    postUpdated: {
      subscribe: () => pubsub.asyncIterator('POST_UPDATED'),
    },
  },
  //Hard-coding the default resolvers is appropriate in some cases, 
  // but I think it is required in fields with references to other 
  //database models to avoid returning null field values.
  Post: {
    id: async (parent, args, contextValue, info) => {
      return parent.id
    },
    title: async (parent) => {
      return parent.title
    },
    postedBy: async (parent) => {
      const user = await User.findById(parent.postedBy)
        .populate('posts', { id: 1, title: 1, comments: 1, postedBy: 1 })
        .populate('commentsMade')
      //console.log('id', user.id)
      //console.log('email', user.email)
      return user
    },
    comments: async (parent) => {
      return parent.comments
    },
  },
  Comment: {
    id: async (parent, args, contextValue, info) => {
      return parent.id
    },
    text: async (parent, args, contextValue, info) => {
      return parent.text
    },
    commenter: async (parent, args, contextValue, info) => {
      const user = await User.findById(parent.commenter)
        .populate('posts')
        .populate('commentsMade')
      return user
    },
    commentFor: async (parent, args, contextValue, info) => {
      const post = await Post.findById(parent.commentFor)
        .populate('comments')
        .populate('postedBy')
      return post
    },
  },
  ...
}

export default resolvers

主入口服务器文件(例如index.js)中的代码可能如下所示,例如

import dotenv from 'dotenv'
import { ApolloServer } from '@apollo/server'
import { expressMiddleware } from '@apollo/server/express4'
import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer'
import { makeExecutableSchema } from '@graphql-tools/schema'
import { WebSocketServer } from 'ws'
import { useServer } from 'graphql-ws/lib/use/ws'
import express from 'express'
import http from 'http'
import cors from 'cors'
import bodyParser from 'body-parser'
import jwt from 'jsonwebtoken'
import UserModel from './models/User.js'
import typeDefs from './tpeDefs.js'
import resolvers from './resolvers.js'
import mongoose from 'mongoose'
dotenv.config()

mongoose.set('strictQuery', false)
let db_uri
if (process.env.NODE_ENV === 'development') {
  db_uri = process.env.MONGO_DEV
}
mongoose.connect(db_uri).then(
  () => {
    console.log('Database connected')
  },
  (err) => {
    console.log(err)
  }
)
const startGraphQLServer = async () => {
  const app = express()
  const httpServer = http.createServer(app)
  const schema = makeExecutableSchema({ typeDefs, resolvers })
  const wsServer = new WebSocketServer({
    server: httpServer,
    path: '/',
  })
  const serverCleanup = useServer({ schema }, wsServer)
  const server = new ApolloServer({
    schema,
    context: async ({ req }) => {
      const authHeader = req.headers['authorization']
      const token = authHeader && authHeader.split(' ')[1]
      if (token) {
        const decoded = jwt.verify(token, process.env.TOKEN_SECRET)
        const authUser = await UserModel.findById(decoded.id)
          .populate('commentsMade')
          .populate('posts')
        return { authUser }
      }
    },
    plugins: [
      ApolloServerPluginDrainHttpServer({ httpServer }),
      {
        async serverWillStart() {
          return {
            async drainServer() {
              await serverCleanup.dispose()
            },
          }
        },
      },
    ],
  })
  await server.start()
  app.use(
    '/',
    cors(),
    bodyParser.json(),
    expressMiddleware(server, {
      context: async ({ req }) => {
        const authHeader = req.headers['authorization']
        const token = authHeader && authHeader.split(' ')[1]
        if (token) {
          const decoded = jwt.verify(token, process.env.TOKEN_SECRET)
          const authUser = await UserModel.findById(decoded.id)
            .populate('commentsMade')
            .populate('posts')
          return { authUser }
        }
      },
    })
  )
  const PORT = 4000
  httpServer.listen(PORT, () =>
    console.log(`Server is now running on http://localhost:${PORT}`)
  )
}
startGraphQLServer()


现在是在Apollo Explorer沙箱中运行一些测试和检查的时候了。请认真定义所需的默认解析器,以避免Apollo服务器代表您发送空值。
要查看代码和实现,go to this repository.
编码快乐!

相关问题