@occupop/lib-graphql v1.0.0
Package @occupop/lib-graphql
This package is intended to be used with @occupop/lib-container
being a wrapper for Apollo Server, providing factory for server and some helpers.
This implementation refers to https://www.apollographql.com/docs/apollo-server! Check their docs for detailed/advanced usage.
NPM registry token required!
Install
# Yarn
yarn add @occupop/lib-graphql
# NPM
npm install @occupop/lib-graphql
# Bun
bun add @occupop/lib-graphql
Env Setup
ENV variables suggested
API_PORT=4000
Register
import {
makeGraphqlServer, graphqlMiddleware, graphqlStandalone
} from '@occupop/lib-graphql'
import { resolvers } from './app/resolvers'
//... all other imports omited...
export const container = createTypedContainer({
// ...
graphqlServer: { asFunction: makeGraphqlServer, singleton: true },
graphqlSchema: { asFunction: () => readFileSync(path.resolve(__dirname, 'src/schema.graphql')), singleton: true },
graphqlResolvers: { asValue: resolvers },
graphqlStandalone: { asValue: graphqlStandalone }, // if startStandaloneServer needed
graphqlMiddleware: { asValue: graphqlMiddleware }, // if expressMiddleware needed
// ...
})
About the helpers
The helpers startStandaloneServer and expressMiddleware can be used as graphqlStandalone and graphqlMiddleware (alias is just for naming convenience).
Usage (starting server)
Standalone server
// server.ts
import { container } from '.container'
const { graphqlServer, graphqlStandalone } = container.cradle
const httpPort = process.env.API_PORT || 4000
await await graphqlStandalone(graphqlServer, {
context: async ({ req }) => ({ userToken: req.headers.token }),
listen: { port: httpPort },
})
console.log(`Server listening on http://localhost:${httpPort}`)
Over Express
// server.ts
import { container } from '.container'
const { httpServer, graphqlServer, graphqlMiddleware } = container.cradle
await graphqlServer.start() // server require start() call...
const httpPort = process.env.API_PORT || 4000
await await httpServer.use('/graphql', graphqlMiddleware(graphqlServer, {
context: async ({ req }) => ({ token: req.headers.token }),
}))
await new Promise(resolve => httpServer.listen({ port: httpPort }, resolve))
console.log(`Server listening on http://localhost:${httpPort}`)
Usage (schema & resolver)
# app/schema.graphql
type Query {
jobs: [Job]
}
type Job {
uuid: String
title: String
}
// app/resolvers.ts
type AppContext = { userToken: string }
export const resolvers: GraphQLResolverMap<AppContext> = {
Query: {
jobs: (source, args, { userToken }, info) => {
authCheck(userToken)
return jobs
},
},
}
Context function & value
Context function passed to the start server helpers are essential to keep user navigation scope under control.
the ideal/recommended way of implementing context function with @occupop/lib-container
is to create a scoped
container and extract the user data from request (token, headers, User, etc) to be passed to context. This way,
graphql-resolvers and container-resolvers will receive those injected values to be treated as necessary.
Some usage notes about context signature:
- Treat graphql-resolvers as routers, they should know how to call an useCase/controller.
- Treat useCases and Controllers as orchestrators they should treat/validate input, user/auth and call appropriate services.
- Treat Services as business rules dealer, they know how to deal with its own minimum responsibilities
but not aware of who called. Remember:
- Services can always be called from queues/commands/events).
- Clean code: if a service is doing too much, maybe he is bad designed.
Sample (real life) context function
import { container } from '.container'
export type AppContext = {
container: typeof container // for router scoped resolve only...
}
export async function contextFunction(req, res): AppContext {
const scoped = container.createScope()
const { authService } = container.cradle
const token = req.headers?.Authorization
scoped.register({ authUser: asValue(await authService.getUser(token)) })
return { container: scoped }
}
8 months ago