Skip to content

Commit

Permalink
feat: Add wundergraph to compose graphql APIs (#64)
Browse files Browse the repository at this point in the history
Co-authored-by: valia fetisov <[email protected]>
  • Loading branch information
KirillDogadin-std and valiafetisov authored Sep 5, 2023
1 parent d06fdb0 commit 5eb82a6
Show file tree
Hide file tree
Showing 22 changed files with 25,166 additions and 12 deletions.
15 changes: 15 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,18 @@ jobs:
run: npm run lint
- name: Build
run: npm run build

wundergraph-lint:
runs-on: ubuntu-latest
defaults:
run:
working-directory: 'wundergraph'
steps:
- uses: actions/checkout@v3
- name: Use Node 18.14.2
uses: actions/setup-node@v3
with:
node-version: 18.14.2
- run: npm install
- run: npm run lint

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Open-source API over the [document model](https://github.com/makerdao-ses/docume
## Project structure
- [`/api`](./api) directory contains the core logic that creates the API over the database
- [`/frontend`](./frontend) directory contains the frontend-related code
- [`./wundergraph`](./wundergraph) directory contains service to compose multiple graphql endpoints together (e.g.: only created by `api` service and one by [ecosystem-api](https://github.com/makerdao-ses/ecosystem-api))

## Quick start

Expand Down
1 change: 1 addition & 0 deletions api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Note: you can set environment variables directly or define them in the `api/.env
- `JWT_SECRET` (required): server's jwt secret
- `PORT` (optional, default `3001`): port on which the server will run
- `API_ORIGIN` (optional, default `http://0.0.0.0:${PORT}`): the URL at which the API is running. it's important to provide this variable in production since it influences the message signed during authorization
- `API_GQL_ENDPOINT` (optional, default `${API_ORIGIN}/graphql`): the graphql URL which will be used in the graphql playground as a server address
- `AUTH_SIGNUP_ENABLED` (optional, default: `false`): if signing up is allowed. In case it's not set, new users _cannot_ be created, but old users _can_ still sign in
- `JWT_EXPIRATION_PERIOD` (optional, default: `'7d'`): how soon JWT token will expire
- `DEBUG` (optional, default not set): if set, enables more explicit logging mode where debug levels are set to `debug` for the app's logger and `query` for db logger
Expand Down
4 changes: 2 additions & 2 deletions api/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import express from 'express';
import expressPlayground from 'graphql-playground-middleware-express';
import { getChildLogger } from './logger';
import prisma from './database';
import { API_ORIGIN } from './env';
import { API_GQL_ENDPOINT } from './env';

const logger = getChildLogger({ msgPrefix: 'APP' });
const startupTime = new Date();
Expand Down Expand Up @@ -33,7 +33,7 @@ export const createApp = (): Express => {
app.get(
'/',
expressPlayground({
endpoint: `${API_ORIGIN}/backend/graphql`,
endpoint: API_GQL_ENDPOINT,
settings: {
'editor.theme': 'light',
'request.credentials': 'include',
Expand Down
1 change: 1 addition & 0 deletions api/src/env/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export const isDevelopment = process.env.NODE_ENV === 'development';
export const AUTH_SIGNUP_ENABLED = Boolean(process.env.AUTH_SIGNUP_ENABLED);
export const JWT_EXPIRATION_PERIOD: string = getJwtExpirationPeriod();
export const API_ORIGIN = process.env.API_ORIGIN || `http://0.0.0.0:${PORT}`;
export const API_GQL_ENDPOINT = process.env.API_GQL_ENDPOINT || `${API_ORIGIN}/graphql`;
22 changes: 12 additions & 10 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,6 @@ services:
timeout: 5s
retries: 5

db_migrations:
build:
context: ./api
command: "npx --yes prisma db push --accept-data-loss"
environment:
DATABASE_URL: "postgresql://postgres:postgres@database:5432/postgres?sslmode=disable&connect_timeout=30"
depends_on:
database:
condition: service_healthy

api:
restart: unless-stopped
expose:
Expand All @@ -52,6 +42,18 @@ services:
expose:
- 3000

wundergraph:
platform: linux/amd64
restart: unless-stopped
expose:
- 3002
build:
context: ./wundergraph
environment:
SWITCHBOARD_GQL_ENDPOINT: "http://api:3000/graphql"
depends_on:
- api

reverse-proxy:
restart: unless-stopped
image: nginx:stable
Expand Down
2 changes: 2 additions & 0 deletions frontend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ WORKDIR /app/frontend
# Set env vars before build
ARG API_BASE=/backend
ENV API_BASE=${API_BASE}
ARG API_GQL_ENDPOINT=/wundergraph/graphql
ENV API_GQL_ENDPOINT=${API_BASE}

RUN npm run build

Expand Down
13 changes: 13 additions & 0 deletions nginx.conf
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
events {}

http {
log_format proxy_log '[$time_local] Proxy: "$proxy_host" "$upstream_addr" '
'$remote_addr - $remote_user "$host$request_uri" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

server {
# frontend
location / {
proxy_pass http://frontend:3000;
}

# api
location /backend/ {
rewrite /backend/(.*) /$1 break;
proxy_pass http://api:3000;
proxy_set_header Host $host;
}

# graphql API composition
location /wundergraph/graphql {
rewrite /wundergraph/graphql /graphql break;
proxy_pass http://wundergraph:3002;
proxy_set_header Host $host;
}
}
}
2 changes: 2 additions & 0 deletions wundergraph/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
.wundergraph/generated
1 change: 1 addition & 0 deletions wundergraph/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
!/.wundergraph
28 changes: 28 additions & 0 deletions wundergraph/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
// From: https://github.com/airbnb/javascript/issues/451 - we want airbnb, but not it's react rules (because we don't use react)
"extends": [
"airbnb-base",
"airbnb-typescript/base"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"ignorePatterns": ["**/generated/*"],
"plugins": [
"@typescript-eslint"
],
"root": true,
"rules": {
"no-return-await": [
"error"
],
"import/prefer-default-export": "off",
"no-console": [
"error"
],
"max-len": ["error", { "code": 120 }],
"quotes": "off"
}
}

2 changes: 2 additions & 0 deletions wundergraph/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# wundergraph
.wundergraph/generated
Empty file.
57 changes: 57 additions & 0 deletions wundergraph/.wundergraph/wundergraph.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {
configureWunderGraphApplication, cors, introspect, EnvironmentVariable, LoggerLevel,
} from '@wundergraph/sdk';
import dotenv from 'dotenv';
import server from './wundergraph.server';
import operations from './wundergraph.operations';

dotenv.config();
const ecosystemGqlEndpoint = process.env.ECOSYSTEM_GQL_ENDPOINT;
if (!ecosystemGqlEndpoint) {
throw new Error('ECOSYSTEM_GQL_ENDPOINT environment variable is not set');
}
const switchboardGqlEndpoint = process.env.SWITCHBOARD_GQL_ENDPOINT || 'http://localhost:3001/graphql';
const allowedOrigins = (process.env.ALLOWED_ORIGINS || 'http://localhost:3001,http://localhost:3000').split(',');

const ecosystem = introspect.graphql({
apiNamespace: 'ecosystem',
url: ecosystemGqlEndpoint,
introspection: {
disableCache: true,
},
});

const switchboard = introspect.graphql({
apiNamespace: '',
url: switchboardGqlEndpoint,
headers: (builder) => builder.addClientRequestHeader('Authorization', 'Authorization'),
introspection: {
disableCache: true,
},
});

// configureWunderGraph emits the configuration
configureWunderGraphApplication({
options: {
listen: {
host: new EnvironmentVariable('NODE_HOST', '0.0.0.0'),
port: new EnvironmentVariable('NODE_PORT', '3002'),
},
logger: {
level: new EnvironmentVariable<LoggerLevel>('NODE_LOG_LEVEL', 'debug'),
},
},
apis: [switchboard, ecosystem],
server,
operations,
generate: {
codeGenerators: [],
},
cors: {
...cors.allowAll,
allowedOrigins,
},
security: {
enableGraphQLEndpoint: true,
},
});
26 changes: 26 additions & 0 deletions wundergraph/.wundergraph/wundergraph.operations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { configureWunderGraphOperations } from '@wundergraph/sdk';
import type { OperationsConfiguration } from './generated/wundergraph.operations';

export default configureWunderGraphOperations<OperationsConfiguration>({
operations: {
defaultConfig: {
authentication: {
required: false,
},
},
queries: (config) => ({
...config,
liveQuery: {
enable: true,
pollingIntervalSeconds: 1,
},
}),
mutations: (config) => ({
...config,
}),
subscriptions: (config) => ({
...config,
}),
custom: {},
},
});
17 changes: 17 additions & 0 deletions wundergraph/.wundergraph/wundergraph.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { configureWunderGraphServer, EnvironmentVariable, LoggerLevel } from '@wundergraph/sdk/server';

export default configureWunderGraphServer(() => ({
options: {
listen: {
host: new EnvironmentVariable('SERVER_HOST', '0.0.0.0'),
port: new EnvironmentVariable('SERVER_PORT', '3003'),
},
logger: {
level: new EnvironmentVariable<LoggerLevel>('SERVER_LOG_LEVEL', 'debug'),
},
},
hooks: {
queries: {},
mutations: {},
},
}));
23 changes: 23 additions & 0 deletions wundergraph/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# see https://docs.docker.com/engine/reference/builder/#understand-how-arg-and-from-interact
ARG NODE_VERSION=node:16.14.2

FROM $NODE_VERSION AS dependency-base

# create destination directory
RUN mkdir -p /app
WORKDIR /app

# copy the app, note .dockerignore
COPY package.json .
COPY package-lock.json .
RUN npm ci

FROM dependency-base AS production

COPY . .

# Run in production mode
ENV NODE_ENV=production

# initialize db and start the app
CMD npm run build && npm run start
21 changes: 21 additions & 0 deletions wundergraph/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Wundergraph service

The service to compose multiple graphql endpoints together. Documentation is of the underlying package is available under https://docs.wundergraph.com/. This service is intended to be run only in production to merge graphql endpoint produced by the `../api` service with [ecosystem-api](https://github.com/makerdao-ses/ecosystem-api)

## Quick start

1. Create env file with correct env variables (start with `cp example.env .env`)
2. Make sure both services that should be composed are running, e.g.:
- Run `npm run dev` in `../api` directory of this repo
- Set correct `ECOSYSTEM_GQL_ENDPOINT` env variable in the file created above
3. Run `npm run dev` inside `./wundergraph` directory
4. Interact with graphql endpoint of wundergraph running at `http://localhost:3002/graphql`
- E.g.: run `npm run dev` in `../frontend` directory of the project to be able to interact with newly created endpoint (you will need to set playground to connect to `http://localhost:3002/graphql`)

## Environment variables

- `ECOSYSTEM_GQL_ENDPOINT` (required): URL of the graphql endpoint that needs to be migrated (in our case that should be `https://ecosystem-dashboard.herokuapp.com/graphql`, but for testing we can use any other graphql endpoint such as `https://countries.trevorblades.com/graphql`). Pre-requirements for the endpoint are:
- Enabled graphql introspection
- Appropriate CORS settings
- `SWITCHBOARD_GQL_ENDPOINT` (optional, default `http://localhost:3001/graphql`): URL of the switchboard graphql endpoint that is proxied transparently
- `ALLOWED_ORIGINS` (optional, default `http://localhost:3001,http://localhost:3000`)
3 changes: 3 additions & 0 deletions wundergraph/example.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
SWITCHBOARD_GQL_ENDPOINT="http://localhost:3001/"
ECOSYSTEM_GQL_ENDPOINT="https://countries.trevorblades.com/graphql"
ALLOWED_ORIGINS="http://localhost:3001,http://localhost:3000"
Loading

0 comments on commit 5eb82a6

Please sign in to comment.