Skip to content

Commit

Permalink
Replace express-graphql with GraphQL Helix (#311)
Browse files Browse the repository at this point in the history
  • Loading branch information
koistya authored Jan 25, 2022
1 parent 35f2329 commit 82a7175
Show file tree
Hide file tree
Showing 16 changed files with 1,140 additions and 1,108 deletions.
779 changes: 0 additions & 779 deletions .yarn/releases/yarn-3.2.0-rc.11.cjs

This file was deleted.

784 changes: 784 additions & 0 deletions .yarn/releases/yarn-3.2.0-rc.12.cjs

Large diffs are not rendered by default.

10 changes: 3 additions & 7 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
packageExtensions:
"@mui/core@*":
dependencies:
"@mui/system": ^5.0.6
"@mui/system": ^5.3.0
"@mui/styled-engine@*":
dependencies:
"@emotion/styled": ^11.3.0
"@emotion/styled": ^11.6.0
argon2@*:
dependencies:
node-gyp: ^8.4.1
Expand All @@ -19,9 +19,5 @@ plugins:
spec: "@yarnpkg/plugin-interactive-tools"
- path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs
spec: "@yarnpkg/plugin-typescript"
- path: .yarn/plugins/@yarnpkg/plugin-bundle.cjs
spec: "https://yarnplugins.com/bundle"
- path: .yarn/plugins/@yarnpkg/plugin-push.cjs
spec: "https://yarnplugins.com/push"

yarnPath: .yarn/releases/yarn-3.2.0-rc.11.cjs
yarnPath: .yarn/releases/yarn-3.2.0-rc.12.cjs
2 changes: 1 addition & 1 deletion api/core/logging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import { Logging } from "@google-cloud/logging";
import { Request } from "express";
import type { GraphQLParams } from "express-graphql";
import type { GraphQLParams } from "graphql-helix";
import PrettyError from "pretty-error";
import env from "../env";

Expand Down
7 changes: 7 additions & 0 deletions api/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,10 @@ declare global {
}
}
}

declare module "graphql" {
interface GraphQLFormattedError {
status?: number;
errors?: Record<string, string[]>;
}
}
110 changes: 59 additions & 51 deletions api/graphql.ts
Original file line number Diff line number Diff line change
@@ -1,70 +1,78 @@
/* SPDX-FileCopyrightText: 2016-present Kriasoft <hello@kriasoft.com> */
/* SPDX-License-Identifier: MIT */

import type { Request } from "express";
import { graphqlHTTP } from "express-graphql";
import fs from "fs";
import type { NextFunction, Request, Response } from "express";
import { GraphQLError, printSchema } from "graphql";
import {
formatError,
GraphQLObjectType,
GraphQLSchema,
printSchema,
} from "graphql";
getGraphQLParameters,
processRequest,
renderGraphiQL,
sendResult,
shouldRenderGraphiQL,
} from "graphql-helix";
import { HttpError } from "http-errors";
import { noop } from "lodash";
import fs from "node:fs/promises";
import { ValidationError } from "validator-fluent";
import { Context } from "./context";
import { reportError } from "./core";
import env from "./env";
import * as mutations from "./mutations";
import * as queries from "./queries";
import { nodeField, nodesField } from "./types/node";
import { ValidationError } from "./utils";
import schema from "./schema";

/**
* GraphQL API schema.
*/
export const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: "Root",
description: "The top-level API",
// Customize GraphQL error serialization
GraphQLError.prototype.toJSON = ((serialize) =>
function toJSON(this: GraphQLError) {
// The original serialized GraphQL error output
const output = serialize.call(this as GraphQLError);

fields: {
node: nodeField,
nodes: nodesField,
...queries,
},
}),
// Append the `HttpError` code
if (this.originalError instanceof HttpError) {
output.status = this.originalError.statusCode;
}

// Append the list of user input validation errors
if (this.originalError instanceof ValidationError) {
output.status = 400;
output.errors = this.originalError.errors;
}

mutation: new GraphQLObjectType({
name: "Mutation",
fields: mutations,
}),
});
return output;
})(GraphQLError.prototype.toJSON);

/**
* GraphQL middleware for Express.js
*/
export const graphql = graphqlHTTP((req, res, params) => ({
schema,
context: new Context(req as Request),
graphiql: env.APP_ENV !== "prod",
pretty: !env.isProduction,
customFormatErrorFn: (err) => {
if (err.originalError instanceof ValidationError) {
res.statusCode = 400;
return { ...formatError(err), errors: err.originalError.errors };
}
async function handleGraphQL(req: Request, res: Response, next: NextFunction) {
try {
if (shouldRenderGraphiQL(req)) {
res.send(renderGraphiQL({ endpoint: "/api" }));
} else {
const params = getGraphQLParameters(req);
const result = await processRequest<Context>({
operationName: params.operationName,
query: params.query,
variables: params.variables,
request: req,
schema,
contextFactory: () => new Context(req),
});

if (err.originalError instanceof HttpError) {
res.statusCode = err.originalError.statusCode;
sendResult(result, res, (result) => ({
data: result.data,
errors: result.errors?.map((err) => {
if (!(err.originalError instanceof ValidationError)) {
reportError(err, req, params);
}
return err;
}),
}));
}
} catch (err) {
next(err);
}
}

reportError(err.originalError || err, req as Request, params);
return formatError(err);
},
}));

export function updateSchema(cb?: fs.NoParamCallback): void {
function updateSchema(): Promise<void> {
const output = printSchema(schema);
fs.writeFile("./schema.graphql", output, { encoding: "utf-8" }, cb || noop);
return fs.writeFile("./schema.graphql", output, { encoding: "utf-8" });
}

export { handleGraphQL, updateSchema };
8 changes: 4 additions & 4 deletions api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import { NotFound } from "http-errors";
import { auth } from "./auth";
import env from "./env";
import { handleError } from "./errors";
import { graphql, updateSchema } from "./graphql";
import { handleGraphQL, updateSchema } from "./graphql";
import { withViews } from "./views";

SendGrid.setApiKey(env.SENDGRID_API_KEY);

export const api = withViews(express());
const api = withViews(express());

api.enable("trust proxy");
api.disable("x-powered-by");
Expand All @@ -22,7 +22,7 @@ api.disable("x-powered-by");
api.use(auth);

// GraphQL API middleware
api.use("/api", graphql);
api.use("/api", express.json(), handleGraphQL);

api.get("/", (req, res) => {
res.render("home");
Expand Down Expand Up @@ -60,4 +60,4 @@ if (process.env.NODE_ENV === "development") {
});
}

export { env, updateSchema };
export { api, env, updateSchema };
10 changes: 5 additions & 5 deletions api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"api:deploy": "yarn workspace api run deploy"
},
"dependencies": {
"@google-cloud/logging": "^9.6.7",
"@google-cloud/logging": "^9.6.8",
"@google-cloud/storage": "^5.18.0",
"@sendgrid/mail": "^7.6.0",
"argon2": "^0.28.3",
Expand All @@ -28,12 +28,12 @@
"date-fns-tz": "^1.2.2",
"envalid": "^7.2.2",
"express": "^4.17.2",
"express-graphql": "^0.12.0",
"express-handlebars": "^6.0.2",
"gm": "^1.23.1",
"google-auth-library": "^7.11.0",
"got": "^12.0.1",
"graphql": "^16.2.0",
"graphql-helix": "^1.11.0",
"graphql-relay": "^0.10.0",
"handlebars": "^4.7.7",
"http-errors": "^2.0.0",
Expand All @@ -53,7 +53,7 @@
},
"devDependencies": {
"@babel/cli": "^7.16.8",
"@babel/core": "^7.16.10",
"@babel/core": "^7.16.12",
"@babel/plugin-proposal-class-properties": "^7.16.7",
"@babel/preset-env": "^7.16.11",
"@babel/preset-typescript": "^7.16.7",
Expand All @@ -78,7 +78,7 @@
"@types/jsonwebtoken": "^8.5.8",
"@types/lodash": "^4.14.178",
"@types/minimist": "^1.2.2",
"@types/node": "^17.0.10",
"@types/node": "^17.0.12",
"@types/pg": "^8.6.4",
"@types/simple-oauth2": "^4.1.1",
"@types/supertest": "^2.0.11",
Expand All @@ -88,7 +88,7 @@
"fs-extra": "^10.0.0",
"knex-types": "^0.3.2",
"prettier": "^2.5.1",
"rollup": "^2.64.0",
"rollup": "^2.66.1",
"rollup-plugin-copy": "^3.4.0",
"rollup-plugin-delete": "^2.0.0",
"supertest": "^6.2.2",
Expand Down
2 changes: 1 addition & 1 deletion api/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ const config = {
},
],

external: Object.keys(pkg.dependencies).filter(
external: [...Object.keys(pkg.dependencies), /^node:/].filter(
// Bundle modules that do not properly support ES
(dep) => !["@sendgrid/mail", "http-errors"].includes(dep),
),
Expand Down
2 changes: 1 addition & 1 deletion api/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ schema {
mutation: Mutation
}

"""The top-level API"""
"""The top-level GraphQL API."""
type Root {
"""Fetches an object given its ID"""
node(
Expand Down
28 changes: 28 additions & 0 deletions api/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/* SPDX-FileCopyrightText: 2016-present Kriasoft <hello@kriasoft.com> */
/* SPDX-License-Identifier: MIT */

import { GraphQLObjectType, GraphQLSchema } from "graphql";
import * as mutations from "./mutations";
import * as queries from "./queries";
import { nodeField, nodesField } from "./types/node";

/**
* GraphQL API schema.
*/
export default new GraphQLSchema({
query: new GraphQLObjectType({
name: "Root",
description: "The top-level GraphQL API.",

fields: {
node: nodeField,
nodes: nodesField,
...queries,
},
}),

mutation: new GraphQLObjectType({
name: "Mutation",
fields: mutations,
}),
});
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0.0.0",
"private": true,
"type": "module",
"packageManager": "yarn@3.2.0-rc.11",
"packageManager": "yarn@3.2.0-rc.12",
"workspaces": [
"api",
"db",
Expand All @@ -25,7 +25,7 @@
},
"dependencies": {
"@babel/cli": "^7.16.8",
"@babel/core": "^7.16.10",
"@babel/core": "^7.16.12",
"@babel/node": "^7.16.8",
"@babel/plugin-proposal-class-properties": "^7.16.7",
"@babel/plugin-proposal-object-rest-spread": "^7.16.7",
Expand All @@ -37,13 +37,13 @@
"@emotion/babel-plugin": "^11.7.2",
"@emotion/eslint-plugin": "^11.7.0",
"@emotion/react": "^11.7.1",
"@typescript-eslint/eslint-plugin": "^5.10.0",
"@typescript-eslint/parser": "^5.10.0",
"@typescript-eslint/eslint-plugin": "^5.10.1",
"@typescript-eslint/parser": "^5.10.1",
"babel-jest": "^27.4.6",
"babel-plugin-import": "^1.13.3",
"babel-plugin-relay": "^13.0.1",
"cross-spawn": "^7.0.3",
"dotenv": "^14.2.0",
"dotenv": "^14.3.0",
"eslint": "^8.7.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-jsx-a11y": "^6.5.1",
Expand All @@ -63,7 +63,7 @@
"@types/cross-spawn": "^6.0.2",
"@types/inquirer": "^8.1.3",
"@types/jest": "^27.4.0",
"@types/node": "^17.0.10"
"@types/node": "^17.0.12"
},
"jest": {
"testPathIgnorePatterns": [
Expand Down
4 changes: 2 additions & 2 deletions scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@
"gh:delete-deployment": "node --no-warnings ./github.js delete-deployment"
},
"dependencies": {
"@babel/core": "^7.16.10",
"@babel/core": "^7.16.12",
"@babel/register": "^7.16.9",
"@google-cloud/storage": "^5.18.0",
"@octokit/rest": "^18.12.0",
"api": "workspace:*",
"cross-spawn": "^7.0.3",
"envars": "^0.3.0",
"formdata-node": "^4.3.2",
"globby": "^11.1.0",
"globby": "^13.1.0",
"got": "^12.0.1",
"make-dir": "^3.1.0",
"minimist": "^1.2.5",
Expand Down
10 changes: 5 additions & 5 deletions setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
* @copyright 2016-present Kriasoft (https://git.io/Jt7GM)
*/

const fs = require("fs");
const crypto = require("crypto");
const dotenv = require("dotenv");
const spawn = require("cross-spawn");
const inquirer = require("inquirer");
import spawn from "cross-spawn";
import crypto from "crypto";
import dotenv from "dotenv";
import inquirer from "inquirer";
import fs from "node:fs";

const environments = {
prod: "production",
Expand Down
Loading

0 comments on commit 82a7175

Please sign in to comment.