Skip to content

Commit

Permalink
feat: add graphql gateway
Browse files Browse the repository at this point in the history
  • Loading branch information
temarusanov committed Jun 29, 2023
1 parent 83f6021 commit 7ec442b
Show file tree
Hide file tree
Showing 26 changed files with 2,515 additions and 120 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
PORT=3000
GATEWAY_PORT=3052
SAMPLE_DATABASE_URL=postgres://user:secret@localhost:5432/postgres?schema=public
3 changes: 2 additions & 1 deletion .nxignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
schema.graphql
schema.graphql
supergraph.graphql
1 change: 1 addition & 0 deletions apps/api/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { ApolloDriver } from '@nestjs/apollo'
driver: ApolloDriver,
autoSchemaFile: '../../schema.graphql',
playground: false,
introspection: true,
plugins: [ApolloServerPluginLandingPageLocalDefault()],
}),
SampleModule.forRoot({
Expand Down
18 changes: 18 additions & 0 deletions apps/gateway/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
19 changes: 19 additions & 0 deletions apps/gateway/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Install dependencies only when needed
FROM docker.io/node:lts-alpine as deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine
# to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /usr/src/app
COPY ./node_modules ./node_modules

# Production image, copy all the files and run nest

FROM docker.io/node:lts-alpine as runner
RUN apk add --no-cache dumb-init
ENV NODE_ENV production
ENV PORT 3000
WORKDIR /usr/src/app
COPY --from=deps /usr/src/app/node_modules ./node_modules
COPY dist/apps/gateway .
EXPOSE 3000
CMD ["dumb-init", "node", "main.js"]
15 changes: 15 additions & 0 deletions apps/gateway/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/* eslint-disable */
export default {
displayName: 'gateway',
preset: '../../jest.preset.js',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
},
},
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'js', 'html'],
}
82 changes: 82 additions & 0 deletions apps/gateway/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
{
"name": "gateway",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/gateway/src",
"projectType": "application",
"targets": {
"build": {
"executor": "@nx/webpack:webpack",
"dependsOn": ["graphql:compose"],
"outputs": ["{options.outputPath}"],
"options": {
"target": "node",
"compiler": "tsc",
"outputPath": "dist/apps/gateway",
"main": "apps/gateway/src/main.ts",
"tsConfig": "apps/gateway/tsconfig.app.json",
"assets": ["apps/gateway/src/assets"]
},
"configurations": {
"production": {
"optimization": true,
"extractLicenses": true,
"inspect": false,
"fileReplacements": [
{
"replace": "apps/gateway/src/environments/environment.ts",
"with": "apps/gateway/src/environments/environment.prod.ts"
}
]
}
}
},
"serve": {
"executor": "@nx/js:node",
"options": {
"buildTarget": "gateway:build"
},
"configurations": {
"production": {
"buildTarget": "gateway:build:production"
}
}
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/gateway/**/*.ts"]
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "apps/gateway/jest.config.ts",
"passWithNoTests": true
}
},
"graphql:compose": {
"executor": "schematics:graphql-gateway-compose"
},
"container": {
"executor": "@nx-tools/nx-container:build",
"dependsOn": ["build"],
"options": {
"engine": "docker",
"metadata": {
"images": ["nx/gateway"],
"load": true,
"tags": [
"type=schedule",
"type=ref,event=branch",
"type=ref,event=tag",
"type=ref,event=pr",
"type=sha,prefix=sha-"
]
}
}
}
},
"tags": []
}
Empty file.
38 changes: 38 additions & 0 deletions apps/gateway/src/assets/supergraph.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
schema
@core(feature: "https://specs.apollo.dev/core/v0.2"),
@core(feature: "https://specs.apollo.dev/join/v0.1", for: EXECUTION)
{
query: Query
}

directive @core(as: String, feature: String!, for: core__Purpose) repeatable on SCHEMA

directive @join__field(graph: join__Graph, provides: join__FieldSet, requires: join__FieldSet) on FIELD_DEFINITION

directive @join__graph(name: String!, url: String!) on ENUM_VALUE

directive @join__owner(graph: join__Graph!) on INTERFACE | OBJECT

directive @join__type(graph: join__Graph!, key: join__FieldSet) repeatable on INTERFACE | OBJECT

type Query {
samplePing: String! @join__field(graph: API)
}

enum core__Purpose {
"""
`EXECUTION` features provide metadata necessary to for operation execution.
"""
EXECUTION

"""
`SECURITY` features provide metadata necessary to securely resolve fields.
"""
SECURITY
}

scalar join__FieldSet

enum join__Graph {
API @join__graph(name: "api" url: "http://localhost:3000/graphql")
}
3 changes: 3 additions & 0 deletions apps/gateway/src/environments/environment.prod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const environment = {
production: true,
}
3 changes: 3 additions & 0 deletions apps/gateway/src/environments/environment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const environment = {
production: false,
}
79 changes: 79 additions & 0 deletions apps/gateway/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Logger } from '@nestjs/common'
import { ApolloServer } from '@apollo/server'
import { startStandaloneServer } from '@apollo/server/standalone'
import { ApolloGateway } from '@apollo/gateway'
import { readFileSync } from 'fs'
const logger = new Logger('Application')

//do something when app is closing
process.on('exit', exitHandler.bind(null, { cleanup: true }))

//catches ctrl+c event
process.on('SIGINT', exitHandler.bind(null, { exit: true }))

// catches "kill pid" (for example: nodemon restart)
process.on('SIGUSR1', exitHandler.bind(null, { exit: true }))
process.on('SIGUSR2', exitHandler.bind(null, { exit: true }))

//catches uncaught exceptions
process.on('uncaughtException', exitHandler.bind(null, { exit: true }))

import env from 'env-var'
import { join } from 'path'

//do something when app is closing
process.on('exit', exitHandler.bind(null, { cleanup: true }))

//catches ctrl+c event
process.on('SIGINT', exitHandler.bind(null, { exit: true }))

// catches "kill pid" (for example: nodemon restart)
process.on('SIGUSR1', exitHandler.bind(null, { exit: true }))
process.on('SIGUSR2', exitHandler.bind(null, { exit: true }))

//catches uncaught exceptions
process.on('uncaughtException', exitHandler.bind(null, { exit: true }))

async function bootstrap() {
const port = env.get('GATEWAY_PORT').default(3052).asPortNumber()

const server = new ApolloServer({
gateway: new ApolloGateway({
supergraphSdl: readFileSync(
join(__dirname, 'assets', 'supergraph.graphql'),
).toString(),
}),
})

const { url } = await startStandaloneServer(server, {
listen: {
port,
},
})

logger.log(`Gateway ready at ${url}`)
}

try {
bootstrap().catch((err) => {
logger.error(err, err.stack)
})
} catch (err) {
logger.error(err, err.stack)
}

function exitHandler(options, exitCode) {
if (options.cleanup) {
logger.log('exit: clean')
}
if (exitCode || exitCode === 0) {
if (exitCode !== 0) {
logger.error(exitCode, exitCode.stack)
} else {
logger.log(`exit: code - ${exitCode}`)
}
}
if (options.exit) {
process.exit()
}
}
5 changes: 5 additions & 0 deletions apps/gateway/supergraph.config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
subgraphs:
api:
routing_url: http://localhost:3000/graphql
schema:
file: ./../api/schema.graphql
12 changes: 12 additions & 0 deletions apps/gateway/tsconfig.app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["node"],
"emitDecoratorMetadata": true,
"target": "es2015"
},
"exclude": ["jest.config.ts", "**/*.spec.ts", "**/*.test.ts"],
"include": ["**/*.ts"]
}
13 changes: 13 additions & 0 deletions apps/gateway/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}
9 changes: 9 additions & 0 deletions apps/gateway/tsconfig.spec.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": ["jest.config.ts", "**/*.test.ts", "**/*.spec.ts", "**/*.d.ts"]
}
5 changes: 5 additions & 0 deletions libs/schematics/executors.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
"implementation": "./src/executors/prisma/migrate/executor",
"schema": "./src/executors/prisma/migrate/schema.json",
"description": "Run Prisma migrations"
},
"graphql-gateway-compose": {
"implementation": "./src/executors/graphql/gateway-compose/executor",
"schema": "./src/executors/graphql/gateway-compose/schema.json",
"description": "Run Apollo Rover supergraph composer"
}
}
}
29 changes: 29 additions & 0 deletions libs/schematics/src/executors/graphql/gateway-compose/executor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { GraphQLGatewayComposeExecutorSchema } from './schema'
import { getExecOutput } from '@actions/exec'

export default async function runExecutor(
options: GraphQLGatewayComposeExecutorSchema,
) {
const args: string[] = []

options.config
? args.push(`--config=${options.config}`)
: args.push(`--config=apps/gateway/supergraph.config.yaml`)

options.output
? args.push(`-o=${options.output}`)
: args.push(`-o=apps/gateway/src/assets/supergraph.graphql`)

await getExecOutput(`rover supergraph compose`, args, {
ignoreReturnCode: true,
}).then((res) => {
if (res.stderr.length > 0 && res.exitCode != 0) {
throw new Error(`${res.stderr.trim() ?? 'unknown error'}`)
}
})

return {
success: true,
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface GraphQLGatewayComposeExecutorSchema {
config?: string
output?: string
} // eslint-disable-line
18 changes: 18 additions & 0 deletions libs/schematics/src/executors/graphql/gateway-compose/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"$schema": "http://json-schema.org/schema",
"version": 2,
"title": "Build executor",
"description": "",
"type": "object",
"properties": {
"config": {
"type": "string",
"description": "The path to the supergraph config"
},
"output": {
"type": "string",
"description": "Output file to generate SDL"
}
},
"required": []
}
Loading

0 comments on commit 7ec442b

Please sign in to comment.