Skip to content

Commit

Permalink
feat: new options structure and added apollo-server-options
Browse files Browse the repository at this point in the history
BREAKING CHANGES: constructor and start options have changed

Closes #11, closes #16, closes #71
  • Loading branch information
kbrandwijk committed Jan 5, 2018
1 parent a825073 commit 1d96b82
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 82 deletions.
176 changes: 103 additions & 73 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,61 +1,44 @@
import * as express from 'express'
import { graphqlExpress } from 'apollo-server-express'
import { apolloUploadExpress, GraphQLUpload } from 'apollo-upload-server'
import * as bodyParser from 'body-parser-graphql'
import * as cors from 'cors'
import * as express from 'express'
import { PathParams, RequestHandler, RequestHandlerParams } from 'express-serve-static-core'
import * as fs from 'fs'
import { execute, GraphQLSchema, subscribe } from 'graphql'
import { importSchema } from 'graphql-import'
import * as path from 'path'
import expressPlayground from 'graphql-playground-middleware-express'
import { SubscriptionServer } from 'subscriptions-transport-ws'
import { createServer } from 'http'
import { execute, subscribe, GraphQLSchema } from 'graphql'
import { apolloUploadExpress, GraphQLUpload } from 'apollo-upload-server'
import { graphqlExpress } from 'apollo-server-express'
import { makeExecutableSchema } from 'graphql-tools'
export { PubSub, withFilter } from 'graphql-subscriptions'
import { Props, Options } from './types'
import { createServer } from 'http'
import * as path from 'path'
import { SubscriptionServer } from 'subscriptions-transport-ws'

import { Options, Props } from './types'

export { PubSub, withFilter } from 'graphql-subscriptions'
export { Options }
export { GraphQLServerLambda } from './lambda'

export class GraphQLServer {
express: express.Application
subscriptionServer: SubscriptionServer | null
options: Options
options: Options = {
tracing: { mode: 'http-header' },
port: process.env.PORT ? parseInt(process.env.PORT, 10) : 4000,
endpoint: '/',
subscriptions: '/',
playground: '/',
}
executableSchema: GraphQLSchema
context: any

protected context: any
private middlewares: {
[key: string]: { path?: PathParams; handlers: RequestHandler[] | RequestHandlerParams[] }[],
} = { use: [], get: [], post: [] }

constructor(props: Props) {
const defaultOptions: Options = {
disableSubscriptions: false,
tracing: { mode: 'http-header' },
port: process.env.PORT ? parseInt(process.env.PORT, 10) : 4000,
endpoint: '/',
subscriptionsEndpoint: '/',
playgroundEndpoint: '/',
disablePlayground: false,
}
this.options = { ...defaultOptions, ...props.options }

if (!this.options.disableSubscriptions) {
this.options.subscriptionsEndpoint = undefined
}

this.express = express()

// CORS support
if (this.options.cors) {
this.express.use(cors(this.options.cors))
} else if (this.options.cors !== false) {
this.express.use(cors())
}

this.express.post(
this.options.endpoint,
bodyParser.graphql(),
apolloUploadExpress(this.options.uploads),
)

this.subscriptionServer = null
this.context = props.context

Expand All @@ -66,9 +49,7 @@ export class GraphQLServer {

// read from .graphql file if path provided
if (typeDefs.endsWith('graphql')) {
const schemaPath = path.isAbsolute(typeDefs)
? path.resolve(typeDefs)
: path.resolve(typeDefs)
const schemaPath = path.isAbsolute(typeDefs) ? path.resolve(typeDefs) : path.resolve(typeDefs)

if (!fs.existsSync(schemaPath)) {
throw new Error(`No schema found for path: ${schemaPath}`)
Expand All @@ -77,9 +58,7 @@ export class GraphQLServer {
typeDefs = importSchema(schemaPath)
}

const uploadMixin = typeDefs.includes('scalar Upload')
? { Upload: GraphQLUpload }
: {}
const uploadMixin = typeDefs.includes('scalar Upload') ? { Upload: GraphQLUpload } : {}
this.executableSchema = makeExecutableSchema({
typeDefs,
resolvers: {
Expand All @@ -90,17 +69,27 @@ export class GraphQLServer {
}
}

start(callback: (() => void) = () => null): Promise<void> {
use(...handlers: RequestHandlerParams[]): this
use(path: PathParams, ...handlers: RequestHandlerParams[]): this
use(path?, ...handlers): this {
this.middlewares.use.push({ path, handlers })
return this
}

get(path: PathParams, ...handlers: RequestHandlerParams[]): this {
this.middlewares.get.push({ path, handlers })
return this
}

post(path: PathParams, ...handlers: RequestHandlerParams[]): this {
this.middlewares.post.push({ path, handlers })
return this
}

start(options?: Options, callback: ((options: Options) => void) = () => null): Promise<void> {
const app = this.express

const {
port,
endpoint,
disablePlayground,
disableSubscriptions,
playgroundEndpoint,
subscriptionsEndpoint,
} = this.options
this.options = { ...this.options, ...options }

const tracing = (req: express.Request) => {
const t = this.options.tracing
Expand All @@ -113,15 +102,48 @@ export class GraphQLServer {
}
}

// CORS support
if (this.options.cors) {
app.use(cors(this.options.cors))
} else if (this.options.cors !== false) {
app.use(cors())
}

app.post(this.options.endpoint, bodyParser.graphql(), apolloUploadExpress(this.options.uploads))

if (this.options.uploads) {
app.post(this.options.endpoint, apolloUploadExpress(this.options.uploads))
}

while (this.middlewares.use.length > 0) {
const middleware = this.middlewares.use.shift()
if (middleware.path) {
app.use(middleware.path, ...middleware.handlers)
} else {
app.use(...middleware.handlers)
}
}

while (this.middlewares.get.length > 0) {
const middleware = this.middlewares.get.shift()
if (middleware.path) {
app.get(middleware.path, ...middleware.handlers)
}
}

while (this.middlewares.post.length > 0) {
const middleware = this.middlewares.post.shift()
if (middleware.path) {
app.post(middleware.path, ...middleware.handlers)
}
}

app.post(
endpoint,
this.options.endpoint,
graphqlExpress(async request => {
let context
try {
context =
typeof this.context === 'function'
? await this.context({ request })
: this.context
context = typeof this.context === 'function' ? await this.context({ request }) : this.context
} catch (e) {
console.error(e)
throw e
Expand All @@ -130,36 +152,46 @@ export class GraphQLServer {
return {
schema: this.executableSchema,
tracing: tracing(request),
cacheControl: this.options.cacheControl,
formatError: this.options.formatError,
logFunction: this.options.logFunction,
rootValue: this.options.rootValue,
validationRules: this.options.validationRules,
fieldResolver: this.options.fieldResolver,
formatParams: this.options.formatParams,
formatResponse: this.options.formatResponse,
debug: this.options.debug,
context,
}
}),
)

if (!disablePlayground) {
const isDev =
process.env.NODE_ENV === 'dev' || process.env.NODE_ENV === 'development'
if (this.options.playground) {
const isDev = process.env.NODE_ENV === 'dev' || process.env.NODE_ENV === 'development'
const playgroundOptions = isDev
? { useGraphQLConfig: true, env: process.env }
: { endpoint, subscriptionsEndpoint }
: this.options.subscriptions
? { endpoint: this.options.endpoint, subscriptionsEndpoint: this.options.subscriptions }
: { endpoint: this.options.endpoint }

app.get(playgroundEndpoint, expressPlayground(playgroundOptions))
app.get(this.options.playground, expressPlayground(playgroundOptions))
}

if (!this.executableSchema) {
throw new Error('No schema defined')
}

return new Promise((resolve, reject) => {
if (disableSubscriptions) {
app.listen(port, () => {
callback()
if (!this.options.subscriptions) {
app.listen(this.options.port, () => {
callback(this.options)
resolve()
})
} else {
const combinedServer = createServer(app)

combinedServer.listen(port, () => {
callback()
combinedServer.listen(this.options.port, () => {
callback(this.options)
resolve()
})

Expand All @@ -172,9 +204,7 @@ export class GraphQLServer {
let context
try {
context =
typeof this.context === 'function'
? await this.context({ connection })
: this.context
typeof this.context === 'function' ? await this.context({ connection }) : this.context
} catch (e) {
console.error(e)
throw e
Expand All @@ -184,7 +214,7 @@ export class GraphQLServer {
},
{
server: combinedServer,
path: subscriptionsEndpoint,
path: this.options.subscriptions,
},
)
}
Expand Down
35 changes: 26 additions & 9 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { Request } from 'express'
import { CorsOptions } from 'cors'
import { GraphQLSchema, GraphQLFieldResolver, GraphQLScalarType, GraphQLIsTypeOfFn, GraphQLTypeResolver } from 'graphql'
import {
GraphQLSchema,
GraphQLFieldResolver,
GraphQLScalarType,
GraphQLIsTypeOfFn,
GraphQLTypeResolver,
ValidationContext,
} from 'graphql'
import { SubscriptionOptions } from 'graphql-subscriptions/dist/subscriptions-manager'
import { LogFunction } from 'apollo-server-core'

export interface IResolvers {
[key: string]: (() => any) | IResolverObject | GraphQLScalarType
Expand Down Expand Up @@ -37,24 +45,33 @@ export interface TracingOptions {
mode: 'enabled' | 'disabled' | 'http-header'
}

export interface Options {
cors?: CorsOptions | false
disableSubscriptions?: boolean
export interface ApolloServerOptions {
tracing?: boolean | TracingOptions
cacheControl?: boolean
formatError?: Function
logFunction?: LogFunction
rootValue?: any
validationRules?: Array<(context: ValidationContext) => any>
fieldResolver?: GraphQLFieldResolver<any, any>
formatParams?: Function
formatResponse?: Function
debug?: boolean
}

export interface Options extends ApolloServerOptions {
port?: number
cors?: CorsOptions | false
uploads?: UploadOptions | false
endpoint?: string
subscriptionsEndpoint?: string
playgroundEndpoint?: string
disablePlayground?: boolean
uploads?: UploadOptions
subscriptions?: string | false
playground?: string | false
}

export interface Props {
typeDefs?: string
resolvers?: IResolvers
schema?: GraphQLSchema
context?: Context | ContextCallback
options?: Options
}

export interface LambdaProps {
Expand Down

0 comments on commit 1d96b82

Please sign in to comment.