A GraphQL server implementation written in NodeJS/Typescript. It uses the standard graphql library to receive GraphQL requests and send back appropriate responses.
Deprecated: This library will no longer be maintained. Use graphql-yoga or graphql-http instead!
npm install --save @dreamit/graphql-serverTypeScript declarations are provided within the project.
The following table shows which version of graphql-js, @dreamit/graphql-server-base and @dreamit/funpara are compatible with which version of
@dreamit/graphql-server. As @dreamit/graphql-server defines them as peerDependency you might want to
choose a fitting version used in your project and by other libraries depending
on them.
| graphql-js version | graphql-server version | graphql-server-base version | funpara version | Github branch | Development Status |
|---|---|---|---|---|---|
| end of life | |||||
| end of life | |||||
| ^16.0.0 | 3.x | ^1.0.1 | n.a. | legacy-server-v3 | end of life |
| ^16.0.0 | 4.x | ^2.7 | ^1.0 | legacy-server-v4 | maintenance |
| ^16.0.0 | 5.x | ^3.1 | ^1.0 | main | active |
- Creates GraphQL responses
- Can be used with many webservers (see Webserver compatibility).
- Supports both
application/graphql-response+jsonandapplication/jsonresponse formats - Uses out-of-the-box default options to ease use and keep code short
- Provides hot reloading for schema and options
- Provides out-of-the-box metrics for GraphQLServer
- Provides option to validate response against StandardSchemaV1 compliant schema.
- Uses only 3 peerDependencies: graphql-js version 16, graphql-server-base version 3 and funpara version 1 (no other production dependencies)
GraphQLServer provides the function handleRequest to handle and execute requests.
Depending on the provided parameters different actions will be executed in order to send or return the ExecutionResult
- request: If the request is a
GraphQLServerRequestthe asyncextractInformationFromRequestfunction will be used to extract information from the request (url and/or body) and be available asGraphQLRequestInfo. If the request already is aGraphQLRequestInfothis information will be used without extracting information from the server request. - response: If a response is provided (i.e. not undefined), a response will be sent using
sendResponsefunction and theGraphQLExecutionResultwill be returned. If response is undefined, no response will be sent and theGraphQLExecutionResultwill be returned.
class GraphQLServer {
async handleRequest(
request: GraphQLServerRequest | GraphQLRequestInfo,
response?: GraphQLServerResponse,
): Promise<GraphQLExecutionResult> {}
}The handleRequest function can be used for many use cases. The following part lists some use cases with a short
description. It is possible to use handleRequest with different parameters with a single GraphQLServer instance,
e.g. when using a webserver with websockets or messaging.
handleRequestwithGraphQLServerRequestandGraphQLServerResponse: Use as webserver middleware. Create an instance ofGraphQLServerand use the request and response provided by the webserver as parameters. You might need to wrap one or both values, see Webserver compatibilityhandleRequestwithGraphQLRequestInfo: Use for flexible GraphQL execution, e.g. for websockets or messaging. Create an instance ofGraphQLServerand given aGraphQLRequestInfothe request can be executed and the returnedGraphQLExecutionResultcan be used for multiple purposes like sending a message or responding to a websocket request.handleRequestwithGraphQLServerRequest: Use as alternative webserver middleware or if custom actions should be done before sending back a response. Create an instance ofGraphQLServerand use the request provided by the webserver as parameter for this function. You might need request values, see Webserver compatibility. The returnedGraphQLExecutionResultcan be used to execute custom logic with the result and/or prepare or send a response.handleRequestwithGraphQLRequestInfoandGraphQLServerResponse: Use if aGraphQLRequestInfois available and a response should be sent from this request.
You can create a new instance of GraphQLServer with the options necessary for your tasks. The
handleRequest function of the GraphQLServer can be integrated with many fitting webservers.
Note regarding POST requests:
graphql-server version 5 and higher tries to extract the request information from the request.body field or response.text function if request.body is undefined. Some webserver
frameworks like Express might need a fitting body parser in order to populate this body field.
- parse body as
string/text(recommended): graphql-server will handle reading content and parsing it to JSON. - parse body as
object/JSON: graphql-server will read JSON and try to assign it to matching fields. This might cause FetchErrors if the body contains invalid JSON. We recommend using text parsers instead so graphql-server can respond with a fitting GraphQL error response if JSON is invalid.
const graphQLServerPort = 3592
const graphQLServerExpress = express()
const customGraphQLServer = new GraphQLServer({ schema: someExampleSchema })
graphQLServerExpress.use(bodyParser.text({ type: '*/*' }))
graphQLServerExpress.all('/graphql', (req, res) => {
return customGraphQLServer.handleRequest(req, res)
})
graphQLServerExpress.listen({ port: graphQLServerPort })
console.info(`Starting GraphQL server on port ${graphQLServerPort}`)GraphQLServer provides default values and behavior out of the box. It is recommended to at least provide a schema
so the request won't be rejected because of a missing/invalid schema. When using it with a local schema it is
recommended to provide a rootValue to return a fitting value. Examples for these requests can be found in the
integration test in the GraphQLServer.integration.test.ts class in the tests/server folder.
GraphQLServer supports two response formats:
application/json: Default, used for legacy GraphQL server and client support. Will return status code 200 for some error cases like parsing and validation errors.application/graphql-response+json: New format recommended by GraphQL specification. Will return status code 400 for all error cases.
Which response format will be used depends on the set accept request header. If the header is not set, set to application/json or application/json with a higher priority this format will be used. application/graphql-response+json will be used only if it is set in accept header or set with higher priority then application/json.
Validation rules can be used to define how the GraphQLServer should behave when validating the request against the
given schema. To ease the use GraphQLServer uses the specifiedRules from graphql-js library. If you don't want
to use the default validation rules you can overwrite them by setting defaultValidationRules option to [].
Warning!
Setting both defaultValidationRules and customValidationRules options to [] will disable validation. This might
result in unexpected responses that are hard to use for API users or frontends.
import { NoSchemaIntrospectionCustomRule } from 'graphql'
const graphQLServerPort = 3592
const graphQLServerExpress = express()
const customGraphQLServer = new GraphQLServer({
schema: someExampleSchema,
defaultValidationRules: [],
})
graphQLServerExpress.use(bodyParser.text({ type: '*/*' }))
graphQLServerExpress.all('/graphql', (req, res) => {
return customGraphQLServer.handleRequest(req, res)
})
graphQLServerExpress.listen({ port: graphQLServerPort })
console.info(`Starting GraphQL server on port ${graphQLServerPort}`)If you want to define custom validation rules you can use the customValidationRules option (e.g. to handle
introspection like shown in the example below).
Introspection can be used to get information about the available schema. While this may be useful in development environments and public APIs you should consider disabling it for production if e.g. your API is only used with a specific matching frontend.
Introspection can be disabled by adding the NoSchemaIntrospectionCustomRule from the graphql-js library to the
customValidationRules option.
import { NoSchemaIntrospectionCustomRule } from 'graphql'
const graphQLServerPort = 3592
const graphQLServerExpress = express()
const customGraphQLServer = new GraphQLServer({
schema: someExampleSchema,
customValidationRules: [NoSchemaIntrospectionCustomRule],
})
graphQLServerExpress.use(bodyParser.text({ type: '*/*' }))
graphQLServerExpress.all('/graphql', (req, res) => {
return customGraphQLServer.handleRequest(req, res)
})
graphQLServerExpress.listen({ port: graphQLServerPort })
console.info(`Starting GraphQL server on port ${graphQLServerPort}`)The GraphQL response can be validated using the responseStandardSchema option. It accepts a StandardSchemaV1 and will, when using sendResponse, put found issues into the response as GraphQLErrors. By default noOpStandardSchema is used and returns the value and no issues. To validate the response a StandardSchemaV1 compliant schema like @dreamit/graphql-std-schema can be used.
import { graphQLResponseSchema } from '@dreamit/graphql-std-schema'
const graphQLServerPort = 3592
const graphQLServerExpress = express()
const customGraphQLServer = new GraphQLServer({
responseStandardSchema: graphQLResponseSchema(),
schema: someExampleSchema,
})
graphQLServerExpress.use(bodyParser.text({ type: '*/*' }))
graphQLServerExpress.all('/graphql', (req, res) => {
return customGraphQLServer.handleRequest(req, res)
})
graphQLServerExpress.listen({ port: graphQLServerPort })
console.info(`Starting GraphQL server on port ${graphQLServerPort}`)Hot reload of the GraphQL schema can be used to update the existing schema to a new version without restarting the GraphQL server, webserver or whole application. When setting a new schema it will be used for the next incoming request while the old schema will be used for requests that are being processed at the moment. Hot reloading is especially useful for remote schemas that are processed in another application like a webservice.
The schema can be changed simply by calling setSchema in the GraphQLServer instance. In the example below a second
route is used to trigger a schema update.
const graphQLServerPort = 3592
const graphQLServerExpress = express()
const customGraphQLServer = new GraphQLServer({ schema: someExampleSchema })
graphQLServerExpress.use(bodyParser.text({ type: '*/*' }))
graphQLServerExpress.all('/graphql', (req, res) => {
return customGraphQLServer.handleRequest(req, res)
})
graphQLServerExpress.all('/updateme', (req, res) => {
const updatedSchema = someMagicHappened()
customGraphQLServer.setSchema(updatedSchema)
return res.status(200).send()
})
graphQLServerExpress.listen({ port: graphQLServerPort })
console.info(`Starting GraphQL server on port ${graphQLServerPort}`)There are 2 builtin MetricsClient implementations available.
- SimpleMetricsClient: Used as default
MetricsClient. Provides GraphQLServer related metrics without but does not provide NodeJS metrics like cpu and memory usage. - NoMetricsClient: Does not collect any metrics. Can be used to disable metrics collection/increase performance.
The SimpleMetricsClient provides three custom metrics for the GraphQL server:
- graphql_server_availability: Availability gauge with status 0 (unavailable) and 1 (available)
- graphql_server_request_throughput: The number of incoming requests
- graphql_server_errors: The number of errors that are encountered while running the GraphQLServer. The counter uses
the errorName field as label so errors could be differentiated. At the moment the following labels are available and
initialized with 0:
- FetchError
- GraphQLError
- SchemaValidationError
- MethodNotAllowedError
- InvalidSchemaError
- MissingQueryParameterError
- ValidationError
- SyntaxError
- IntrospectionDisabledError
A simple metrics endpoint can be created by using getMetricsContentType and getMetrics functions from
the GraphQLServer instance. In the example below a second route is used to return metrics data.
const graphQLServerPort = 3592
const graphQLServerExpress = express()
const customGraphQLServer = new GraphQLServer({ schema: someExampleSchema })
graphQLServerExpress.use(bodyParser.text({ type: '*/*' }))
graphQLServerExpress.all('/graphql', (req, res) => {
return customGraphQLServer.handleRequest(req, res)
})
graphQLServerExpress.get('/metrics', async (req, res) => {
return res
.contentType(customGraphQLServer.getMetricsContentType())
.send(await customGraphQLServer.getMetrics())
})
graphQLServerExpress.listen({ port: graphQLServerPort })
console.info(`Starting GraphQL server on port ${graphQLServerPort}`)The GraphQLServer does not handle CORS requests on its own. It is recommended to handle this on the webserver level,
e.g. by using cors library with an Express webserver like in the example below.
const graphQLServerPort = 3592
const graphQLServerExpress = express()
graphQLServerExpress.use(cors())
const customGraphQLServer = new GraphQLServer({ schema: someExampleSchema })
graphQLServerExpress.use(bodyParser.text({ type: '*/*' }))
graphQLServerExpress.all('/graphql', (req, res) => {
return customGraphQLServer.handleRequest(req, res)
})
graphQLServerExpress.listen({ port: graphQLServerPort })
console.info(`Starting GraphQL server on port ${graphQLServerPort}`)The handleRequest function works with webservers that provide a fitting request and
response object that matches GraphQLServerRequest and GraphQLServerResponse interface. As Express (since
version 2.x) matches both no further adjustment is necessary. If one or both objects do not match GraphQLServerRequest
and GraphQLServerResponse
it might still be possible to map the webserver request and response objects to these interfaces.
In the following table a list of webserver frameworks/versions can be found that are able to run GraphQLServer.
The Version column shows the version of the webserver framework we tested GraphQLServer version 4 with.
If the request and/or response has to be mapped it is noted in the Mapping column. There is a code
examples on how to use handleRequest without providing a GraphQLServerResponse and sending the response with the
functionality provided by the webserver.
| Framework/Module | Version | Mapping | Example |
|---|---|---|---|
| Express | > = 2.x | none | Express example |
| fastify | 5.3.2 | none | Fastify example |
| hapi | 21.4.0 | request, no response | hapi example |
| Koa | 3.0.0 | response | Koa example |
| Next.js | 15.3.1 | response | Next.js example |
| Nitro | 4.9.5 | request | Nitro example |
| NodeJS http | 22.x | request | NodeJS http example |
| Socket.IO | 4.7.5 | response | Socket.IO example |
| gRPC | 1.8.14 | no response | gRPC example |
| Deno | 1.42.1 | request, no response | Deno HTTP example |
| Bun | 1.1.0 | request, no response | Bun example |
GraphQLServerRequest and GraphQLServerResponse interfaces
The GraphQLServerRequest and GraphQLServerResponse are available in the @dreamit/graphql-server-base module.
This allows extensions such as custom Logger or MetricsClient implementations to implement these interfaces without
defining @dreamit/graphql-server as dependency.
GraphQLServer, like many GraphQL libraries, uses a context function to create a context object of type Record<string, unknown> that is available during the whole request execution process. This can for example be used to inject information about request headers or adjust responses. An example can be found in the CustomSendResponse.integration.test.ts class in the test/server folder.
export interface GraphQLServerOptions {
contextFunction: (contextParameters: {
serverOptions: GraphQLServerOptions
request?: GraphQLServerRequest
response?: GraphQLServerResponse
}) => Record<string, unknown>
}The GraphQLServer accepts the following GraphQLServerOptions. When calling the constructor or setOptions function, useful defaults from defaultGraphQLServerOptions are set and overwritten by the options provided in the constructor/function parameter.
schema: The schema that is used to handle the request and send a response. If undefined theGraphQLServerwill reject responses with a GraphQL error response with status code 500.shouldUpdateSchemaFunction: Function that can be used to determine whether a schema update should be executed.formatErrorFunction: Function that can be used to format occurring GraphQL errors. Given aGraphQLErrorit should return aGraphQLFormattedError. By defaultdefaultFormatErrorFunctionis called that useserror.toJSONto format the error.schemaValidationFunction: Function that is called when a schema is set or updated. Given aGraphQLSchemait can return aReadonlyArray<GraphQLError>or an empty array if no errors occurred/should be returned. By defaultvalidateSchemafrom graphql-js library is called.parseFunction: Function that is called to create aDocumentNodewith the extracted query in the request information. Given asourceandParseOptionsit should return aDocumentNode. By defaultparsefrom graphql-js library is called.defaultValidationRules: Default validation rules that are used whenvalidateSchemaFunctionis called. BothdefaultValidationRulesandcustomValidationRuleswill be merged together whenvalidateSchemaFunctionis called. By defaultspecifiedRulesfrom graphql-js are used. Can be overwritten if no or other default rules should be used.customValidationRules: Custom validation rules that are used whenvalidateSchemaFunctionis called. BothdefaultValidationRulesandcustomValidationRuleswill be merged together whenvalidateSchemaFunctionis called. By default, an empty array is set. Can be overwritten to add additional rules likeNoSchemaIntrospectionCustomRule.validationTypeInfo: Validation type info that is used whenvalidateSchemaFunctionis called.validationOptions: Validation options containing{ maxErrors?: number }that is used whenvalidateSchemaFunctionis called.removeValidationRecommendations: Iftrueremoves validation recommendations like "users not found. Did you mean user?". For non-production environments it is usually safe to allow recommendations. For production environments when not providing access to third-party users it is considered good practice to remove these recommendations so users can not circumvent disabled introspection request by using recommendations to explore the schema.validateFunction: Validation function that validates the extracted request against the available schema. By defaultvalidatefrom graphql-js library is called.rootValue: Root value that is used whenexecuteFunctionis called. Can be used to define resolvers that handle how defined queries and/or mutations should be resolved (e.g. fetch object from database and return entity).fieldResolver: Field resolver function that is used whenexecuteFunctionis called. Default is undefined, if custom logic is necessary it can be added.typeResolver: Type resolver function that is used whenexecuteFunctionis called. Default is undefined, if custom logic is necessary it can be added.executeFunction: Execute function that executes the parsedDocumentNode(created inparseFunction) using given schema, values and resolvers. Returns a Promise or value of anExecutionResult. By defaultexecutefrom graphql-js library is called.extensionFunction: Extension function that can be used to add additional information to theextensionsfield of the response. Given aGraphQLRequestInfo,ExecutionResult,GraphQLServerOptionsand context it should return undefined or an ObjMap of key-value-pairs that are added to theextensionsfield. By defaultdefaultExtensionsis used and returns undefined.reassignAggregateError: Iftrueand theExecutionResultcreated by theexecuteFunctioncontains anAggregateError(e.g. an error containing a comma-separated list of errors in the message and anoriginalErrorcontaining multiple errors) this function will reassign theoriginalError.errorsto theExecutionResult.errorsfield. This is helpful if another application createsAggregateErrorswhile the initiator of the request (e.g. a Frontend app) does not expect or know how to handleAggregateErrors.
contextFunction: GivenGraphQLServerOptions,GraphQLServerRequestandGraphQLServerResponsethis function is used to create a context value of typeRecord<string, unknown>that is available in the whole request flow. Default implementation isdefaultContextFunctionthat returns an empty object. Can be used to extract information from the request and/or response and return them as context. This is often used to extract headers like 'Authorization' and set them in the execute function.
executionResultErrorMessage:: Error message that is used in logging if a response contains anerrorselement.fetchErrorMessage:: If provided and not set to undefined, used as fixed error message if a FetchError occurs.graphqlExecutionErrorMessage:: Error message that is used in logging if an error is thrown whenexecutefunction is called.validationErrorMessage:: Error message that is used in logging if one or more errors occurred when calling thevalidatefunction.
methodNotAllowedResponse:: Function given a method asstringreturns an error that the used method is not allowed byGraphQLServer.invalidSchemaResponse:: Default error that is returned with set schema is invalid.missingQueryParameterResponse:: Default error that is returned if no query is available in theGraphQLRequestInfo.onlyQueryInGetRequestsResponse:: Function given an operation asstringreturns an error that the used operation is not allowed forGETrequests.
collectErrorMetricsFunction:: Given an error name as string, error asunknown,GraphQLServerOptionsand context asRecord<string, unknown>, this function can be used to trigger collecting error metrics. Default implementation isdefaultCollectErrorMetricswhich increases the error counter for the given errorName or Error by 1.
responseStandardSchema::StandardSchemaV1to validate response against. By defaultnoOpStandardSchemais used and returns the value and no issues.
logger: Logger to be used in the GraphQL server.TextLoggerandJsonLoggeras well asNoStacktraceTextLoggerandNoStacktraceJsonLogger(useful for tests without the need for a stacktrace) are available in the module. Own Logger can be created by implementingLoggerinterface. Note:NoLogger(useful if no logging should be done but logger is required) is available in package @dreamit/graphql-testing.extractInformationFromRequest: Async function that can be used to extract information from theGraphQLServerRequestand return aPromise<GraphQLRequestInfo>. By default, theextractInformationFromRequestfunction is used that tries to extract the information from the body (usingrequest.bodyfield orrequest.text()function) and URL params of the request.sendResponse: Function used to send a fitting response being either adataorerrorresponse. By default, thesendResponseis used that tries to create and send a response using the functions provided by the givenGraphQLServerResponse.metricsClient: TheMetricsClientused to collect metrics from the GraphQLServer. By default, theSimpleMetricsClientis used that collects three custom metrics. Own MetricsClient can be used by implementingMetricsClientinterface.responseEndChunkFunction: Function used to adjust the chunk/body before it is used in theresponse.endorresponse.sendfunction call in thesendResponsefunction. By default it stringifies the ExecutionResult.adjustGraphQLExecutionResult: Function used to adjust theGraphQLExecutionResultbefore it is returned.returnNotAcceptableForUnsupportedResponseFormat: Boolean value that when set totruereturns a response with status code "406 Not Acceptable" if the client does not provide anacceptrequest header or the values in the header do not contain either "application/graphql-response+json" or "application/json". Default:false
To make it easier to customize and extend the GraphQLServer classes and class functions are public. This makes extending
a class and overwriting logic easy.
In the example below the logic of TextLogger is changed to add the text "SECRETAPP" in front of every log output.
export class SecretApplicationTextLogger extends TextLogger {
prepareLogOutput(logEntry: LogEntry): string {
return `SECRETAPP - ${super.prepareLogOutput(logEntry)}`
}
}If you have problems, questions or issues please visit our Issue page and open a new issue if there are no fitting issues for your topic yet.
graphql-server is under MIT-License.