Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

10 request response interfaces #11

Merged
merged 4 commits into from
Mar 3, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 29 additions & 4 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
"plugin:@typescript-eslint/recommended",
"plugin:unicorn/recommended",
"plugin:import/recommended",
"plugin:import/typescript"
],
"globals": {
"Atomics": "readonly",
Expand All @@ -17,11 +20,14 @@
"parserOptions": {
"ecmaVersion": 2017,
"sourceType": "module",
"project": ["tsconfig.json"]
"project": "**/tsconfig.json"
},
"plugins": [
"@typescript-eslint/eslint-plugin",
"modules-newline"
"modules-newline",
"unicorn",
"import",
"deprecation"
],
"rules": {
"@typescript-eslint/class-literal-property-style": "error",
Expand Down Expand Up @@ -57,6 +63,25 @@
"no-inline-comments": "error",
"spaced-comment": ["error", "always"],
"line-comment-position": "error",
"lines-around-comment": "error"
"lines-around-comment": "error",
"unicorn/filename-case": [
"error",
{
"case": "pascalCase",
"ignore": [
"^GraphQL",
"\\.test\\.ts$"
]
}
],
"unicorn/custom-error-definition": "error",
"unicorn/no-unsafe-regex": "error",
"unicorn/no-unused-properties": "error",
"unicorn/prefer-at": "error",
"import/no-internal-modules": [ "error", {
"allow": [ "graphql/**" ]
} ],
"import/no-unresolved": 0,
"deprecation/deprecation": "error"
}
}
49 changes: 32 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ TypeScript declarations are provided within the project.

## Compatibility

The following table shows which version of `graphql-js` library is compatible with which version of
`@dreamit/graphql-server`. As `@dreamit/graphql-server` defines `graphql-js` as peerDependency you might want
to choose a fitting version according to the `graphql-js` version used in your project and by other libraries depending
on `graphql-js`.
The following table shows which version of [graphql-js][1] library is compatible with which version of
`@dreamit/graphql-server`. As `@dreamit/graphql-server` defines [graphql-js][1] as peerDependency you might want
to choose a fitting version according to the [graphql-js][1] version used in your project and by other libraries depending
on [graphql-js][1].

| graphql-js version | graphql-server Version | Github branch |
| ------------- |:-------------:| -----:|
Expand All @@ -26,12 +26,12 @@ on `graphql-js`.
## Features

- Creates GraphQL responses for (GraphQL) requests
- Can be use with fitting webservers that provide a matching `Request` and `Response` object
(e.g. ExpressJS).
- Can be use with fitting webservers that provide a matching `GraphQLServerRequest` and `GraphQLServerResponse` object
(e.g. [Express][2]).
- 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
- Uses and is compatible with `graphql-js` library version 15 (graphqlserver 1.x) and 16 (graphqlserver 2.x) .
- Uses and is compatible with [graphql-js][1] library version 15 (graphqlserver 1.x) and 16 (graphqlserver 2.x) .

## Usage

Expand All @@ -58,7 +58,7 @@ integration test in the `GraphQLServer.integration.test.ts` class in the `tests`

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
To ease the use `GraphQLServer` uses the `specifiedRules` from [graphql-js][1] library. If you don't want to use the default
validation rules you can overwrite them by setting `defaultValidationRules` option to `[]`.

**Warning!**
Expand All @@ -85,7 +85,7 @@ Introspection can be used to get information about the available schema. While t
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
Introspection can be disabled by adding the `NoSchemaIntrospectionCustomRule` from the [graphql-js][1] library to the
`customValidationRules` option.

```typescript
Expand Down Expand Up @@ -129,7 +129,7 @@ console.info(`Starting GraphQL server on port ${graphQLServerPort}`)

## Metrics

The implementation uses `prom-client` library to provide default NodeJS metrics as well as three custom metrics for the
The implementation uses [prom-client][3] library to provide default NodeJS metrics as well as 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
Expand Down Expand Up @@ -165,7 +165,7 @@ console.info(`Starting GraphQL server on port ${graphQLServerPort}`)
## CORS requests

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 ExpressJS webserver like in the example below.
e.g. by using `cors` library with an [Express][2] webserver like in the example below.

```typescript
const graphQLServerPort = 3592
Expand All @@ -179,6 +179,16 @@ graphQLServerExpress.listen({port: graphQLServerPort})
console.info(`Starting GraphQL server on port ${graphQLServerPort}`)
```

## Webserver framework compatibility

The `GraphQLServer.handleRequest` function works with webservers that provide a fitting request and response object
that match `GraphQLServerRequest` and `GraphQLServerResponse` interfaces. As [Express][2] 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 implement these interfaces and map the webserver framework to the graphql-server implementations. An example how to
support [fastify][4] can be found in the [fastify-example](https://github.com/sgohlke/fastify-example/blob/main/src/FastifyGraphQLServer.ts)

## Available options

The `GraphQLServer` accepts the following options. Note that all options are optional and can be overwritten by
Expand All @@ -198,13 +208,13 @@ should return a `GraphQLFormattedError`. By default `defaultFormatErrorFunction`
format the error.
- **`schemaValidationFunction`**: Function that is called when a schema is set or updated. Given a `GraphQLSchema` it
can return a `ReadonlyArray<GraphQLError>` or an empty array if no errors occurred/should be returned.
By default `validateSchema` from `graphql-js` library is called.
By default `validateSchema` from [graphql-js][1] library is called.
- **`parseFunction`**: Function that is called to create a `DocumentNode` with the extracted query in the
request information. Given a `source` and `ParseOptions` it should return a `DocumentNode`.
By default `parse` from `graphql-js` library is called.
By default `parse` from [graphql-js][1] library is called.
- **`defaultValidationRules`**: Default validation rules that are used when `validateSchemaFunction` is called.
Both `defaultValidationRules` and `customValidationRules` will be merged together when `validateSchemaFunction`
is called. By default `specifiedRules` from `graphql-js` are used.
is called. By default `specifiedRules` from [graphql-js][1] are used.
Can be overwritten if no or other default rules should be used.
- **`customValidationRules`**: Custom validation rules that are used when `validateSchemaFunction` is called.
Both `defaultValidationRules` and `customValidationRules` will be merged together when `validateSchemaFunction`
Expand All @@ -219,7 +229,7 @@ For production environments when not providing access to third-party users it is
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 default `validate` from `graphql-js` library is called.
By default `validate` from [graphql-js][1] library is called.
- **`rootValue`**: Root value that is used when `executeFunction` is 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).
- **`contextFunction`**: Given a `Request` and `Response` this function is used to create a context value
Expand All @@ -233,7 +243,7 @@ if custom logic is necessary it can be added.
if custom logic is necessary it can be added.
- **`executeFunction`**: Execute function that executes the parsed `DocumentNode` (created in `parseFunction`) using
given schema, values and resolvers. Returns a Promise or value of an `ExecutionResult`.
By default `execute` from `graphql-js` library is called.
By default `execute` from [graphql-js][1] library is called.
- **`extensionFunction`**: Extension function that can be used to add additional information to
the `extensions` field of the response. Given a `Request`, `GraphQLRequestInfo` and `ExecutionResult` it should return
undefined or an ObjMap of key-value-pairs that are added to the`extensions` field. By default `defaultCollectErrorMetrics`
Expand All @@ -252,7 +262,7 @@ the error counter for the given errorName or Error by 1.
extract the information from the body and URL params of the request. Own Extractor can be created by
implementing `RequestInformationExtractor` interface.
- **`metricsClient`**: The `MetricsClient` used to collect metrics from the GraphQLServer. By default,
the `DefaultMetricsClient` is used that collects default NodeJS and three custom metrics using `prom-client` library.
the `DefaultMetricsClient` is used that collects default NodeJS and three custom metrics using [prom-client][3] library.
Own MetricsClient can be used by implementing `MetricsClient` interface.

## Customise and extend GraphQLServer
Expand All @@ -276,3 +286,8 @@ and open a new issue if there are no fitting issues for your topic yet.

## License
graphql-server is under [MIT-License](./LICENSE).

[1]: https://github.com/graphql/graphql-js
[2]: https://expressjs.com/
[3]: https://github.com/siimon/prom-client
[4]: https://www.fastify.io/
15 changes: 9 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@dreamit/graphql-server",
"version": "2.0.1",
"version": "2.1.0",
"description": "A GraphQL server written in NodeJS/Typescript.",
"main": "build/src/index.js",
"types": "build/src/index.d.ts",
Expand Down Expand Up @@ -29,13 +29,16 @@
"devDependencies": {
"@types/content-type": "1.1.5",
"@types/express": "4.17.13",
"@types/jest": "27.4.0",
"@types/node": "17.0.18",
"@typescript-eslint/eslint-plugin": "5.12.0",
"@types/jest": "27.4.1",
"@types/node": "17.0.21",
"@typescript-eslint/eslint-plugin": "5.12.1",
"cross-fetch": "3.1.5",
"eslint": "8.9.0",
"eslint": "8.10.0",
"eslint-plugin-deprecation": "1.3.2",
"eslint-plugin-import": "2.25.4",
"eslint-plugin-modules-newline": "0.0.6",
"express": "4.17.2",
"eslint-plugin-unicorn": "41.0.0",
"express": "4.17.3",
"jest": "27.5.1",
"jest-html-reporters": "3.0.5",
"ts-jest": "27.1.3",
Expand Down
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable import/no-internal-modules */
/**
* A GraphQL server implementation written in NodeJS/Typescript.
* It uses the standard graphql library to receive GraphQL
Expand All @@ -20,3 +21,5 @@ export * from './server/DefaultRequestInformationExtractor'
export * from './server/RequestInformationExtractor'
export * from './server/GraphQLServer'
export * from './server/GraphQLServerOptions'
export * from './server/GraphQLServerRequest'
export * from './server/GraphQLServerResponse'
17 changes: 10 additions & 7 deletions src/logger/JsonLogger.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {Logger} from './Logger'
import {Console} from 'console'
import {Console} from 'node:console'
import {LogLevel} from './LogLevel'
import {LogEntry} from './LogEntry'
import {LogHelper} from './LogHelper'
import {Request} from '../server/GraphQLServer'
import {GraphQLServerRequest} from '..'

const loggerConsole: Console = new Console(process.stdout, process.stderr, false)

Expand All @@ -29,25 +29,28 @@ export class JsonLogger implements Logger {
this.serviceName = serviceName
}

debug(logMessage: string, request?: Request): void {
debug(logMessage: string, request?: GraphQLServerRequest): void {
this.logMessage(logMessage, LogLevel.debug, request)
}

error(logMessage: string, error: Error, customErrorName: string, request?: Request): void {
error(logMessage: string,
error: Error,
customErrorName: string,
request?: GraphQLServerRequest): void {
this.logMessage(logMessage, LogLevel.error, request, error, customErrorName)
}

info(logMessage: string, request?: Request): void {
info(logMessage: string, request?: GraphQLServerRequest): void {
this.logMessage(logMessage, LogLevel.info, request)
}

warn(logMessage: string, request?: Request): void {
warn(logMessage: string, request?: GraphQLServerRequest): void {
this.logMessage(logMessage, LogLevel.warn, request)
}

logMessage(logMessage: string,
loglevel: LogLevel,
request?: Request,
request?: GraphQLServerRequest,
error?: Error,
customErrorName?: string): void {
const logEntry: LogEntry = LogHelper.createLogEntry(logMessage,
Expand Down
5 changes: 3 additions & 2 deletions src/logger/LogHelper.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import {LogLevel} from './LogLevel'
import {LogEntry} from './LogEntry'
import {GraphQLError} from 'graphql'
import {Request} from '../server/GraphQLServer'
import {GraphQLServerRequest} from '..'

export const VARIABLES_IN_MESSAGE_REGEX = new RegExp(/got invalid value (.*); Field/gm)

// eslint-disable-next-line unicorn/no-static-only-class
export class LogHelper {
static createLogEntry(logMessage: string,
loglevel: LogLevel,
loggerName: string,
serviceName: string,
request?: Request,
_request?: GraphQLServerRequest,
error?: Error,
customErrorName?: string): LogEntry {
const logEntry: LogEntry = {
Expand Down
13 changes: 8 additions & 5 deletions src/logger/Logger.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import {Request} from '../server/GraphQLServer'
import {GraphQLServerRequest} from '..'

export interface Logger {
readonly loggerName: string
readonly serviceName: string

debug(logMessage: string, request?: Request): void
error(logMessage: string, error: Error, customErrorName: string, request?: Request): void
info(logMessage: string, request?: Request): void
warn(logMessage: string, request?: Request): void
debug(logMessage: string, request?: GraphQLServerRequest): void
error(logMessage: string,
error: Error,
customErrorName: string,
request?: GraphQLServerRequest): void
info(logMessage: string, request?: GraphQLServerRequest): void
warn(logMessage: string, request?: GraphQLServerRequest): void
}
25 changes: 15 additions & 10 deletions src/logger/TextLogger.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import {Logger} from './Logger'
import {LogLevel} from './LogLevel'
import {LogEntry} from './LogEntry'
import {LogHelper} from './LogHelper'
import {Request} from '../server/GraphQLServer'
import {
Logger,
LogLevel,
LogEntry,
LogHelper,
GraphQLServerRequest
} from '..'

/**
* Logger implementation that outputs log entries as text to console.
Expand All @@ -25,25 +27,28 @@ export class TextLogger implements Logger {
this.serviceName = serviceName
}

debug(logMessage: string, request?: Request): void {
debug(logMessage: string, request?: GraphQLServerRequest): void {
this.logMessage(logMessage, LogLevel.debug, request)
}

error(logMessage: string, error: Error, customErrorName: string, request?: Request): void {
error(logMessage: string,
error: Error,
customErrorName: string,
request?: GraphQLServerRequest): void {
this.logMessage(logMessage, LogLevel.error, request, error)
}

info(logMessage: string, request?: Request): void {
info(logMessage: string, request?: GraphQLServerRequest): void {
this.logMessage(logMessage, LogLevel.info, request)
}

warn(logMessage: string, request?: Request): void {
warn(logMessage: string, request?: GraphQLServerRequest): void {
this.logMessage(logMessage, LogLevel.warn, request)
}

logMessage(logMessage: string,
loglevel: LogLevel,
request?: Request,
request?: GraphQLServerRequest,
error?: Error,
customErrorName?: string): void {
const logEntry: LogEntry = LogHelper.createLogEntry(logMessage,
Expand Down
2 changes: 1 addition & 1 deletion src/metrics/DefaultMetricsClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
SCHEMA_VALIDATION_ERROR,
SYNTAX_ERROR,
VALIDATION_ERROR
} from '../error/ErrorNameConstants'
} from '..'

/**
* Default metrics client to collect metrics from application and GraphQL server.
Expand Down
Loading