Chapter 10: Deployment
In this chapter, we'll explore various deployment strategies and best practices for GraphQL Yoga applications.
Deployment Options
Serverless Deployment
Vercel
Deploy to Vercel:
// api/graphql.ts
import { createYoga } from '@graphql-yoga/node'
import { schema } from '../../schema'
export default createYoga({
schema,
graphqlEndpoint: '/api/graphql'
})
// Configure Next.js
export const config = {
api: {
bodyParser: false
}
}
AWS Lambda
Deploy to AWS Lambda:
// lambda.ts
import { createYoga } from '@graphql-yoga/node'
import { schema } from './schema'
const yoga = createYoga({
schema
})
export const handler = async (event: any) => {
const response = await yoga.fetch('http://localhost:4000/graphql', {
method: event.httpMethod,
headers: event.headers,
body: event.body
})
return {
statusCode: response.status,
headers: Object.fromEntries(response.headers),
body: await response.text()
}
}
Container Deployment
Docker
Create a Dockerfile:
# Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 4000
CMD ["npm", "start"]
Build and run:
docker build -t graphql-yoga-app .
docker run -p 4000:4000 graphql-yoga-app
Kubernetes
Create Kubernetes manifests:
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: graphql-api
spec:
replicas: 3
selector:
matchLabels:
app: graphql-api
template:
metadata:
labels:
app: graphql-api
spec:
containers:
- name: graphql-api
image: graphql-yoga-app:latest
ports:
- containerPort: 4000
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-secrets
key: url
---
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: graphql-api
spec:
selector:
app: graphql-api
ports:
- port: 80
targetPort: 4000
type: LoadBalancer
Environment Configuration
Environment Variables
Manage environment variables:
// config.ts
import { z } from 'zod'
const envSchema = z.object({
NODE_ENV: z.enum(['development', 'production', 'test']),
PORT: z.string().transform(Number),
DATABASE_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
CORS_ORIGIN: z.string().url()
})
export const config = envSchema.parse(process.env)
Secrets Management
Use a secrets manager:
// secrets.ts
import { SecretsManager } from 'aws-sdk'
const secretsManager = new SecretsManager()
export async function getSecret(name: string) {
const secret = await secretsManager.getSecretValue({
SecretId: name
}).promise()
return JSON.parse(secret.SecretString || '{}')
}
Production Best Practices
Security
Implement security measures:
// server.ts
import { createYoga } from '@graphql-yoga/node'
import { useResponseCache } from '@envelop/response-cache'
import { useValidation } from '@envelop/validation'
import { useErrorHandler } from '@envelop/error-handler'
const yoga = createYoga({
schema,
plugins: [
useResponseCache({
ttl: 60 * 1000
}),
useValidation({
rules: [
maxDepth(3),
maxComplexity(1000),
noIntrospection()
]
}),
useErrorHandler({
formatError: (error) => {
// Don't expose internal errors
if (error.originalError instanceof Error) {
return {
message: 'Internal server error',
code: 'INTERNAL_SERVER_ERROR'
}
}
return error
}
})
]
})
CORS Configuration
Configure CORS:
// server.ts
const yoga = createYoga({
schema,
cors: {
origin: process.env.CORS_ORIGIN,
credentials: true,
methods: ['GET', 'POST', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
}
})
Rate Limiting
Implement rate limiting:
// server.ts
import { createYoga } from '@graphql-yoga/node'
import { useRateLimit } from '@envelop/rate-limit'
const yoga = createYoga({
schema,
plugins: [
useRateLimit({
// 100 requests per minute
max: 100,
window: '1m',
// Custom key based on IP and user
keyGenerator: (context) => {
const ip = context.request.headers.get('x-forwarded-for')
const userId = context.user?.id
return `${ip}:${userId}`
}
})
]
})
Monitoring and Logging
Application Monitoring
Set up monitoring:
// server.ts
import { createYoga } from '@graphql-yoga/node'
import { usePrometheus } from '@envelop/prometheus'
import { useSentry } from '@envelop/sentry'
const yoga = createYoga({
schema,
plugins: [
usePrometheus({
path: '/metrics',
metrics: {
queryDuration: {
type: 'histogram',
help: 'Duration of GraphQL queries'
},
errorCount: {
type: 'counter',
help: 'Number of GraphQL errors'
}
}
}),
useSentry({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV
})
]
})
Logging
Implement structured logging:
// logger.ts
import pino from 'pino'
export const logger = pino({
level: process.env.LOG_LEVEL || 'info',
formatters: {
level: (label) => {
return { level: label }
}
}
})
// server.ts
const yoga = createYoga({
schema,
plugins: [
{
onExecute: ({ args }) => {
logger.info({
operation: args.operationName,
variables: args.variableValues
})
},
onExecuteDone: ({ result }) => {
if (result.errors) {
logger.error({
errors: result.errors
})
}
}
}
]
})
CI/CD Pipeline
GitHub Actions
Set up CI/CD with GitHub Actions:
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Build
run: npm run build
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v2
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Deploy to ECS
run: |
aws ecs update-service --cluster prod --service graphql-api --force-new-deployment
Summary
In this chapter, we've covered:
- Deployment options (serverless, containers)
- Environment configuration
- Production best practices
- Monitoring and logging
- CI/CD pipeline setup
These deployment strategies and practices will help you run your GraphQL API reliably in production. In the next chapter, we'll conclude with a summary of what we've learned.