- gRPC service with MusicBrainz provider - PostgreSQL schema with migrations - Service layer with database-first caching - Repository pattern for data access - YAML configuration support - Research documentation for 17 music metadata projects
14 KiB
GraphBrainz Deployment
Deployment Modes
GraphBrainz supports three deployment modes:
| Mode | Use Case | Entry Point |
|---|---|---|
| Standalone Server | Dedicated GraphQL service | cli.js |
| Express Middleware | Embed in existing app | middleware() export |
| Direct GraphQL | Programmatic queries | schema + context exports |
Standalone Server
NPM Package
Package Name: graphbrainz
Installation:
npm install -g graphbrainz
Binary Command:
graphbrainz
Local Development
Installation:
git clone https://github.com/exogen/graphbrainz.git
cd graphbrainz
npm install
Start Server:
npm start
# or
node cli.js
Default Configuration:
- Port: 3000
- Path: /
- GraphiQL: enabled
Environment Variables
| Variable | Default | Purpose |
|---|---|---|
| PORT | 3000 | Server port |
| GRAPHBRAINZ_PATH | / | GraphQL endpoint path |
| GRAPHBRAINZ_CORS_ORIGIN | false | CORS configuration |
| GRAPHBRAINZ_GRAPHIQL | true (dev) | Enable GraphiQL |
| GRAPHBRAINZ_EXTENSIONS | - | Extension list |
| GRAPHBRAINZ_CACHE_SIZE | 8192 | LRU cache size |
| GRAPHBRAINZ_CACHE_TTL | 86400000 | Cache TTL (ms) |
| MUSICBRAINZ_BASE_URL | http://musicbrainz.org/ws/2/ | MusicBrainz API |
| NODE_ENV | development | Environment mode |
Example Configuration
.env:
PORT=4000
GRAPHBRAINZ_PATH=/graphql
GRAPHBRAINZ_CORS_ORIGIN=*
GRAPHBRAINZ_EXTENSIONS=cover-art-archive,fanart,mediawiki,theaudiodb
FANART_API_KEY=your-fanart-key
THEAUDIODB_API_KEY=your-theaudiodb-key
GRAPHBRAINZ_CACHE_SIZE=16384
GRAPHBRAINZ_CACHE_TTL=3600000
Start:
node cli.js
Access:
- GraphQL endpoint: http://localhost:4000/graphql
- GraphiQL interface: http://localhost:4000/graphql
Express Middleware
Installation
npm install graphbrainz
Basic Integration
import express from 'express';
import { middleware } from 'graphbrainz';
const app = express();
app.use('/graphql', middleware());
app.listen(3000, () => {
console.log('Server running on http://localhost:3000/graphql');
});
Advanced Configuration
import express from 'express';
import { middleware } from 'graphbrainz';
import lastfm from 'graphbrainz-extension-lastfm';
const app = express();
app.use('/graphql', middleware({
// Extension configuration
extensions: [
lastfm
],
// Cache configuration
cacheSize: 16384,
cacheTTL: 3600000,
// MusicBrainz configuration
musicbrainz: {
baseURL: 'http://localhost:5000/ws/2/'
},
// Extension API keys
fanart: {
apiKey: process.env.FANART_API_KEY
},
theaudiodb: {
apiKey: process.env.THEAUDIODB_API_KEY
},
// GraphiQL configuration
graphiql: true,
// CORS configuration
cors: {
origin: '*'
}
}));
app.listen(3000);
Multiple Endpoints
import express from 'express';
import { middleware } from 'graphbrainz';
const app = express();
// Public endpoint (no extensions)
app.use('/graphql/public', middleware({
extensions: []
}));
// Premium endpoint (all extensions)
app.use('/graphql/premium', middleware({
extensions: ['cover-art-archive', 'fanart', 'mediawiki', 'theaudiodb']
}));
app.listen(3000);
Direct GraphQL Client
Installation
npm install graphbrainz
Programmatic Queries
import { schema, context } from 'graphbrainz';
import { graphql } from 'graphql';
const query = `
{
lookup {
artist(mbid: "5b11f4ce-a62d-471e-81fc-a69a8278c7da") {
name
country
}
}
}
`;
const result = await graphql({
schema,
source: query,
contextValue: context
});
console.log(result.data);
Custom Context
import { createSchema, createContext } from 'graphbrainz';
const schema = createSchema({
extensions: ['cover-art-archive', 'fanart']
});
const context = createContext({
cacheSize: 16384,
cacheTTL: 3600000,
fanart: {
apiKey: process.env.FANART_API_KEY
}
});
const result = await graphql({
schema,
source: query,
contextValue: context
});
Heroku Deployment
GraphBrainz includes Heroku-specific deployment scripts.
Procfile
File: Procfile
web: node cli.js
Deployment Script
File: scripts/deploy.sh
#!/bin/bash
# Create deploy branch
git checkout -b deploy
# Build schema and docs
npm run update-schema
npm run build-docs
# Commit build artifacts
git add -f schema.json docs/
git commit -m "Build for deployment"
# Force push to Heroku
git push -f heroku deploy:master
# Clean up
git checkout main
git branch -D deploy
Heroku Configuration
Create App:
heroku create my-graphbrainz
Set Environment Variables:
heroku config:set NODE_ENV=production
heroku config:set GRAPHBRAINZ_EXTENSIONS=cover-art-archive,fanart,mediawiki,theaudiodb
heroku config:set FANART_API_KEY=your-key
heroku config:set THEAUDIODB_API_KEY=your-key
heroku config:set GRAPHBRAINZ_CACHE_SIZE=16384
heroku config:set GRAPHBRAINZ_GRAPHIQL=false
Deploy:
./scripts/deploy.sh
Access:
https://my-graphbrainz.herokuapp.com/
Heroku Dyno Sizing
| Dyno Type | Memory | Recommended Load |
|---|---|---|
| Free | 512 MB | Development only |
| Hobby | 512 MB | <10 req/s |
| Standard-1X | 512 MB | <25 req/s |
| Standard-2X | 1 GB | <100 req/s |
| Performance-M | 2.5 GB | <500 req/s |
NPM Package Distribution
Package Exports
File: package.json
{
"name": "graphbrainz",
"version": "9.0.0",
"main": "src/index.js",
"bin": {
"graphbrainz": "cli.js"
},
"exports": {
".": "./src/index.js",
"./schema": "./schema.json",
"./extensions/cover-art-archive": "./src/extensions/cover-art-archive/index.js",
"./extensions/fanart": "./src/extensions/fanart/index.js",
"./extensions/mediawiki": "./src/extensions/mediawiki/index.js",
"./extensions/theaudiodb": "./src/extensions/theaudiodb/index.js"
}
}
Module Imports
// Main module
import { middleware, schema, context } from 'graphbrainz';
// Schema introspection
import schemaJSON from 'graphbrainz/schema';
// Built-in extensions
import coverArt from 'graphbrainz/extensions/cover-art-archive';
import fanart from 'graphbrainz/extensions/fanart';
import mediawiki from 'graphbrainz/extensions/mediawiki';
import theaudiodb from 'graphbrainz/extensions/theaudiodb';
Continuous Integration
Travis CI
File: .travis.yml
language: node_js
node_js:
- "12"
- "14"
- "15"
cache:
directories:
- node_modules
script:
- npm test
- npm run build
after_success:
- npm run coverage
- npx codecov
- npx coveralls < coverage/lcov.info
GitHub Actions (Not Implemented)
GraphBrainz uses Travis CI. Migration to GitHub Actions would look like:
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12, 14, 16, 18]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test
- run: npm run build
- uses: codecov/codecov-action@v3
Build Process
Schema Generation
Command:
npm run update-schema
Script:
import { schema } from './src/index.js';
import { printSchema } from 'graphql';
import fs from 'fs';
const schemaSDL = printSchema(schema);
fs.writeFileSync('schema.graphql', schemaSDL);
const schemaJSON = JSON.stringify(schema.toJSON(), null, 2);
fs.writeFileSync('schema.json', schemaJSON);
Output:
schema.graphql- SDL representationschema.json- Introspection JSON
Documentation Generation
Command:
npm run build-docs
Scripts:
scripts/generate-readme-toc.js- Table of contentsscripts/generate-schema-docs.js- Schema referencescripts/generate-type-docs.js- Type documentationscripts/generate-extension-docs.js- Extension reference
Preversion Hook
File: package.json
{
"scripts": {
"preversion": "npm run update-schema && npm run build-docs && git add schema.json schema.graphql docs/"
}
}
Ensures schema and docs are updated before version bump.
Docker (Not Implemented)
GraphBrainz does not include Docker configuration. Example implementation:
Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
EXPOSE 3000
CMD ["node", "cli.js"]
docker-compose.yml
version: '3.8'
services:
graphbrainz:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- GRAPHBRAINZ_EXTENSIONS=cover-art-archive,fanart,mediawiki,theaudiodb
- FANART_API_KEY=${FANART_API_KEY}
- THEAUDIODB_API_KEY=${THEAUDIODB_API_KEY}
- GRAPHBRAINZ_CACHE_SIZE=16384
restart: unless-stopped
Build and Run
docker-compose up -d
Kubernetes (Not Implemented)
Example Kubernetes deployment:
Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: graphbrainz
spec:
replicas: 3
selector:
matchLabels:
app: graphbrainz
template:
metadata:
labels:
app: graphbrainz
spec:
containers:
- name: graphbrainz
image: graphbrainz:9.0.0
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: "production"
- name: GRAPHBRAINZ_CACHE_SIZE
value: "16384"
- name: FANART_API_KEY
valueFrom:
secretKeyRef:
name: graphbrainz-secrets
key: fanart-api-key
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
Service
apiVersion: v1
kind: Service
metadata:
name: graphbrainz
spec:
selector:
app: graphbrainz
ports:
- port: 80
targetPort: 3000
type: LoadBalancer
Production Considerations
Memory Allocation
Node.js Heap Size:
node --max-old-space-size=2048 cli.js
Recommended Allocation:
| Traffic | Heap Size | Total Memory |
|---|---|---|
| <10 req/s | 512 MB | 1 GB |
| 10-50 req/s | 1 GB | 2 GB |
| 50-100 req/s | 2 GB | 4 GB |
| 100+ req/s | 4 GB | 8 GB |
Process Management
PM2:
npm install -g pm2
pm2 start cli.js --name graphbrainz -i max
pm2 save
pm2 startup
Systemd:
[Unit]
Description=GraphBrainz GraphQL Server
After=network.target
[Service]
Type=simple
User=graphbrainz
WorkingDirectory=/opt/graphbrainz
ExecStart=/usr/bin/node cli.js
Restart=on-failure
Environment=NODE_ENV=production
Environment=PORT=3000
[Install]
WantedBy=multi-user.target
Reverse Proxy
Nginx:
upstream graphbrainz {
server localhost:3000;
}
server {
listen 80;
server_name graphbrainz.example.com;
location / {
proxy_pass http://graphbrainz;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
Monitoring
GraphBrainz does not include built-in monitoring. Recommended additions:
Prometheus Metrics:
import promClient from 'prom-client';
const register = new promClient.Registry();
const httpRequestDuration = new promClient.Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route', 'status_code']
});
register.registerMetric(httpRequestDuration);
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = (Date.now() - start) / 1000;
httpRequestDuration.labels(req.method, req.path, res.statusCode).observe(duration);
});
next();
});
app.get('/metrics', (req, res) => {
res.set('Content-Type', register.contentType);
res.end(register.metrics());
});
Health Checks
GraphBrainz does not include health endpoints. Recommended implementation:
app.get('/health', (req, res) => {
res.json({
status: 'ok',
uptime: process.uptime(),
memory: process.memoryUsage(),
cache: {
size: cache.size,
max: cache.max
}
});
});
app.get('/ready', async (req, res) => {
try {
// Check MusicBrainz connectivity
await fetch(`${process.env.MUSICBRAINZ_BASE_URL}/artist/5b11f4ce-a62d-471e-81fc-a69a8278c7da`);
res.json({ status: 'ready' });
} catch (error) {
res.status(503).json({ status: 'not ready', error: error.message });
}
});
Scaling Strategies
Horizontal Scaling
GraphBrainz is stateless (except LRU cache) and can be horizontally scaled:
Load Balancer:
Client -> Load Balancer -> GraphBrainz Instance 1
-> GraphBrainz Instance 2
-> GraphBrainz Instance 3
Cache Considerations:
- Each instance has independent LRU cache
- Cache hit ratio decreases with more instances
- Consider shared cache (Redis) for better hit ratio
Vertical Scaling
Increase memory allocation for larger cache:
GRAPHBRAINZ_CACHE_SIZE=32768 # 4x default
node --max-old-space-size=4096 cli.js
Local MusicBrainz Mirror
Eliminate rate limits and reduce latency:
MUSICBRAINZ_BASE_URL=http://localhost:5000/ws/2/
Benefits:
- No rate limiting
- <10ms latency (vs 100-500ms)
- Offline operation
- Full dataset access