The Express adapter provides middleware-based integration of go-go-scope with Express applications.
Installation
npm install @go-go-scope/adapter-express go-go-scope express
Quick Start
import express from 'express'
import { goGoScope } from '@go-go-scope/adapter-express'
const app = express()
// Apply middleware
app.use(goGoScope(app, {
name: 'my-api',
timeout: 30000 // Optional: default timeout for all requests
}))
app.get('/users/:id', async (req, res) => {
// Access request-scoped scope
const [err, user] = await req.scope.task(
() => fetchUser(req.params.id),
{ retry: 'exponential', timeout: 5000 }
)
if (err) {
return res.status(500).json({ error: err.message })
}
res.json(user)
})
const server = app.listen(3000)
// Graceful shutdown
process.on('SIGTERM', async () => {
await closeScope(app)
server.close()
})
Configuration Options
name
string
default:"'express-app'"
Name for the root application scope
Default timeout in milliseconds for all request scopes
Middleware Architecture
Root Scope Creation
The middleware creates a single root scope, accessible via app.scope
Request Scope Creation
For each request, a child scope is created with the root as parent, accessible via req.scope
Response Cleanup
Request scopes are disposed on both finish and close events to handle streaming responses
Manual Shutdown
Call closeScope(app) during graceful shutdown to dispose the root scope
Usage Examples
Basic CRUD Operations
app.get('/posts/:id', async (req, res) => {
const [err, post] = await req.scope.task(
() => db.posts.findById(req.params.id)
)
if (err) {
return res.status(404).json({ error: 'Post not found' })
}
res.json(post)
})
Parallel Operations
app.get('/aggregated-data', async (req, res) => {
const userId = req.query.userId
const [err, results] = await req.scope.parallel([
() => fetchUserProfile(userId),
() => fetchUserPosts(userId),
() => fetchUserFollowers(userId)
])
if (err) {
return res.status(500).json({ error: err.message })
}
res.json({
profile: results[0],
posts: results[1],
followers: results[2]
})
})
Streaming Responses
app.get('/stream-logs', async (req, res) => {
const channel = req.scope.channel({ buffer: 50 })
// Producer: tail log file
req.scope.task(async ({ signal }) => {
for await (const line of tailLogFile(signal)) {
await channel.send(line)
}
channel.close()
})
res.setHeader('Content-Type', 'text/plain')
res.setHeader('Transfer-Encoding', 'chunked')
// Consumer: stream to response
for await (const line of channel) {
res.write(line + '\n')
}
res.end()
})
Error Handling Middleware
import { goGoScope, closeScope } from '@go-go-scope/adapter-express'
app.use(goGoScope(app, { name: 'my-api' }))
// Custom error handler
app.use((err, req, res, next) => {
// Scope errors are automatically propagated
if (err.name === 'TimeoutError') {
return res.status(504).json({ error: 'Request timeout' })
}
if (err.name === 'AbortError') {
return res.status(499).json({ error: 'Request cancelled' })
}
res.status(500).json({ error: 'Internal server error' })
})
Background Tasks
const app = express()
app.use(goGoScope(app, { name: 'worker-api' }))
// Start background job using root scope
app.scope.task(async ({ signal }) => {
while (!signal.aborted) {
await processQueue()
await new Promise(r => setTimeout(r, 1000))
}
})
app.listen(3000)
Graceful Shutdown
The adapter provides a closeScope helper for graceful shutdown:
import { closeScope } from '@go-go-scope/adapter-express'
const server = app.listen(3000)
process.on('SIGTERM', async () => {
console.log('Shutting down gracefully...')
// Dispose root scope (cancels all tasks)
await closeScope(app)
// Close HTTP server
server.close(() => {
console.log('Server closed')
process.exit(0)
})
})
Type Augmentation
The adapter automatically augments Express types:
declare global {
namespace Express {
interface Request {
scope: Scope
}
interface Application {
scope: Scope
}
}
}
Request ID Generation
Each request scope has a unique name:
// Format: request-{timestamp}-{random}
const scopeName = `request-${Date.now()}-${Math.random().toString(36).slice(2)}`
For custom request IDs, use a request ID middleware before the scope middleware:
import { v4 as uuidv4 } from 'uuid'
app.use((req, res, next) => {
req.id = uuidv4()
next()
})
app.use(goGoScope(app))
Response Lifecycle
The middleware handles both finish and close events:
// Normal response completion
res.on('finish', () => {
req.scope[Symbol.asyncDispose]().catch(() => {})
})
Best Practices
Register the go-go-scope middleware before route handlers to ensure scopes are available.
Check error values in result tuples and return appropriate HTTP status codes.
Use streaming for large responses
Leverage channels for streaming responses to handle backpressure automatically.
Implement graceful shutdown
Always call closeScope(app) before closing the HTTP server.
Fastify Adapter
Plugin-based Fastify integration
Koa Adapter
Koa middleware integration
NestJS Adapter
Dependency injection for NestJS
Core API
Core go-go-scope concepts