1.0.0 • Published 8 months ago

@occupop/lib-graphql v1.0.0

Weekly downloads
-
License
-
Repository
-
Last release
8 months ago

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 }
}
1.0.0

8 months ago