Skip to content

Commit

Permalink
feat(graphql): add graphql package (#708)
Browse files Browse the repository at this point in the history
  • Loading branch information
dipendraupreti authored Jul 31, 2024
1 parent 1890503 commit 12e916c
Show file tree
Hide file tree
Showing 19 changed files with 682 additions and 2 deletions.
Empty file added packages/graphql/.eslintignore
Empty file.
4 changes: 4 additions & 0 deletions packages/graphql/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: ["custom"],
};
4 changes: 4 additions & 0 deletions packages/graphql/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
**/*.log*
/coverage
/dist
/node_modules
102 changes: 102 additions & 0 deletions packages/graphql/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# @dzangolab/fastify-graphql

A [Fastify](https://github.com/fastify/fastify) plugin that provides an easy integration of mercurius graphql server in a fastify API.

The plugin is a thin wrapper around the [mercurius](https://mercurius.dev/#/) plugin.

## Requirements

* @dzangolab/fastify-config
* @dzangolab/fastify-slonik
* graphql
* [mercurius](https://mercurius.dev/#/)
* mercurius-codegen
* slonik

## Installation

In a simple repo:

```bash
npm install @dzangolab/fastify-graphql graphql mercurius mercurius-codegen
```

If using in a monorepo with pnpm:

```bash
pnpm add --filter "myrepo" @dzangolab/fastify-graphql graphql mercurius mercurius-codegen
```

## Usage

Add a `graphql` block to your config:

```javascript
import { parse } from "@dzangolab/fastify-config";
import dotenv from "dotenv";

import { resolvers, schema } from "path/to/graphql";

import type { ApiConfig } from "@dzangolab/fastify-config";

dotenv.config();

const config: ApiConfig = {
...
graphql: {
enabled: parse(process.env.GRAPHQL_ENABLED, true) as boolean,
graphiql: parse(process.env.GRAPHIQL_ENABLED, false) as boolean,
path: parse(process.env.GRAPHQL_PATH, "/graphql") as string,
resolvers,
schema,
},
...
};

export default config;
```

Register the plugin with your Fastify instance:

```javascript
import configPlugin from "@dzangolab/fastify-config";
import graphqlPlugin from "@dzangolab/fastify-graphql";
import fastify from "fastify";

import config from "./config";

import type { ApiConfig } from "@dzangolab/fastify-config";
import type { FastifyInstance } from "fastify";

// Create fastify instance
const fastify = Fastify({
logger: config.logger,
});

// Register fastify-config plugin
fastify.register(configPlugin, { config });

// Register fastify-graphql plugin
fastify.register(graphqlPlugin);

await fastify.listen({
port: config.port,
host: "0.0.0.0",
});
```

## Configuration

The `graphql` block in the `ApiConfig` supports all of the [original mercurius plugin's options](https://mercurius.dev/#/docs/api/options?id=plugin-options).

An additional `enabled` (boolean) option allows you to disable the graphql server.

## Context

The fastify-graphql plugin will generate a graphql context on every request that will include the following attributes:

| Attribute | Type | Description |
|------------|------|-------------|
| `config` | `ApiConfig` | The fastify servers' config (as per @dzangolab/fastify-config) |
| `database` | `Database` | The fastify server's slonik instance (as per @dzangolab/fastify-slonik) |
| `sql` | `SqlTaggedTemplate` | The fastify server's `sql` tagged template from slonik |
72 changes: 72 additions & 0 deletions packages/graphql/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{
"name": "@dzangolab/fastify-graphql",
"version": "0.69.0",
"description": "Fastify graphql plugin",
"homepage": "https://github.com/dzangolab/fastify/tree/main/packages/graphql#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/dzangolab/fastify.git",
"directory": "packages/graphql"
},
"license": "MIT",
"type": "module",
"exports": {
".": {
"import": "./dist/dzangolab-fastify-graphql.js",
"require": "./dist/dzangolab-fastify-graphql.umd.cjs"
}
},
"main": "./dist/dzangolab-fastify-graphql.umd.cjs",
"module": "./dist/dzangolab-fastify-graphql.js",
"types": "./dist/types/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "vite build && tsc --emitDeclarationOnly && mv dist/src dist/types",
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --ignore-path .gitignore",
"lint:fix": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"sort-package": "npx sort-package-json",
"test": "vitest run --coverage",
"typecheck": "tsc --noEmit -p tsconfig.json --composite false"
},
"dependencies": {
"graphql-tag": "2.12.6",
"@graphql-tools/merge": "9.0.4"
},
"devDependencies": {
"@dzangolab/fastify-config": "0.69.0",
"@dzangolab/fastify-slonik": "0.69.0",
"@types/node": "20.12.2",
"@typescript-eslint/eslint-plugin": "5.62.0",
"@typescript-eslint/parser": "5.62.0",
"@vitest/coverage-istanbul": "2.0.4",
"eslint": "8.56.0",
"eslint-config-custom": "0.69.0",
"fastify": "4.10.2",
"fastify-plugin": "4.4.0",
"graphql": "16.9.0",
"mercurius": "14.1.0",
"prettier": "2.8.8",
"slonik": "38.0.0",
"tsconfig": "0.69.0",
"typescript": "4.9.5",
"vite": "4.5.3",
"vitest": "2.0.4",
"zod": "3.23.8"
},
"peerDependencies": {
"@dzangolab/fastify-config": "0.69.0",
"@dzangolab/fastify-slonik": "0.69.0",
"fastify": ">=4.9.2",
"fastify-plugin": ">=4.3.0",
"graphql": ">=16.9.0",
"mercurius": ">=14.1.0",
"slonik": ">=38.0.0",
"zod": ">=3.23.8"
},
"engines": {
"node": ">=16",
"pnpm": ">=8"
}
}
126 changes: 126 additions & 0 deletions packages/graphql/src/__test__/context.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import fastify from "fastify";
import { describe, expect, it, beforeEach } from "vitest";

import createConfig from "./helpers/createConfig";
import testPlugin from "./helpers/testPlugin";
import testPluginAsync from "./helpers/testPluginAsync";
import graphqlPlugin from "../plugin";

import type { FastifyInstance } from "fastify";

describe("Graphql Context", async () => {
let api: FastifyInstance;

beforeEach(async () => {
api = await fastify();
});

it("Should add context property and value from callback test plugin", async () => {
api.addHook("onRequest", async (request) => {
request.config = createConfig([testPlugin]);
});

api.decorate("config", createConfig([testPlugin]));

api.register(testPlugin);

await api.register(graphqlPlugin);

const response = await api.inject({
method: "POST",
payload: {
operationName: "test",
query: `
query test {
test{
propertyTwo
propertyOne
}
}
`,
},
url: "/graphql",
});

expect(JSON.parse(response.payload).data.test).toEqual({
//eslint-disable-next-line unicorn/no-null
propertyOne: null,
propertyTwo: "Property Two",
});

expect(api).toHaveProperty(["propertyTwo"], "Property Two");
});

it("Should add context property and value from Async test plugin", async () => {
api.addHook("onRequest", async (request) => {
request.config = createConfig([testPluginAsync]);
});

api.decorate("config", createConfig([testPluginAsync]));

await api.register(testPluginAsync);

await api.register(graphqlPlugin);

const response = await api.inject({
method: "POST",
payload: {
operationName: "test",
query: `
query test {
test{
propertyTwo
propertyOne
}
}
`,
},
url: "/graphql",
});

expect(JSON.parse(response.payload).data.test).toEqual({
//eslint-disable-next-line unicorn/no-null
propertyTwo: null,
propertyOne: "Property One",
});

expect(api).toHaveProperty(["propertyOne"], "Property One");
});

it("Should add context property and value from Async/Callback test plugin", async () => {
api.addHook("onRequest", async (request) => {
request.config = createConfig([testPlugin, testPluginAsync]);
});

api.decorate("config", createConfig([testPlugin, testPluginAsync]));

await api.register(graphqlPlugin);
await api.register(testPlugin);
await api.register(testPluginAsync);

const response = await api.inject({
method: "POST",
payload: {
operationName: "test",
query: `
query test {
test{
propertyTwo
propertyOne
}
}
`,
},
url: "/graphql",
});

expect(JSON.parse(response.payload).data.test).toEqual({
propertyTwo: "Property Two",
propertyOne: "Property One",
});

expect(api).toHaveProperty(["propertyTwo"], "Property Two");

expect(api).toHaveProperty(["propertyOne"], "Property One");
});
});
63 changes: 63 additions & 0 deletions packages/graphql/src/__test__/helpers/createConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/* istanbul ignore file */
import type { MercuriusEnabledPlugin } from "../../types";
import type { ApiConfig } from "@dzangolab/fastify-config";
import type { MercuriusContext } from "mercurius";

const schema = `
type Query {
test: Response
}
type Response {
propertyOne: String
propertyTwo: String
}
`;

const resolvers = {
Query: {
test: async (_: unknown, __: unknown, context: MercuriusContext) => ({
propertyOne: context.propertyOne,
propertyTwo: context.propertyTwo,
}),
},
};

const createConfig = (plugins: MercuriusEnabledPlugin[]) => {
const config: ApiConfig = {
appName: "app",
appOrigin: ["http://localhost"],
baseUrl: "http://localhost",
env: "development",
logger: {
level: "debug",
},
name: "Test",
port: 3000,
protocol: "http",
rest: {
enabled: true,
},
version: "0.1",
graphql: {
enabled: true,
graphiql: false,
path: "/graphql",
schema,
resolvers,
plugins,
},
slonik: {
db: {
databaseName: "test",
host: "localhost",
password: "password",
username: "username",
},
},
};

return config;
};

export default createConfig;
Loading

0 comments on commit 12e916c

Please sign in to comment.