The Koa adapter provides middleware-based integration of go-go-scope with Koa’s async/await-first architecture.
Installation
npm install @go-go-scope/adapter-koa go-go-scope koa
Quick Start
import Koa from 'koa'
import { koaGoGoScope } from '@go-go-scope/adapter-koa'
const app = new Koa()
// Apply middleware
app.use(koaGoGoScope({
name: 'koa-api',
timeout: 30000 // Optional: default timeout for all requests
}))
app.use(async (ctx) => {
const scope = ctx.state.scope
const [err, user] = await scope.task(
() => fetchUser(ctx.params.id),
{ retry: 'exponential', timeout: 5000 }
)
if (err) {
ctx.status = 500
ctx.body = { error: err.message }
return
}
ctx.body = user
})
const server = app.listen(3000)
// Graceful shutdown
process.on('SIGTERM', async () => {
await closeKoaScope()
server.close()
})
Configuration Options
name
string
default:"'koa-app'"
Name for the root application scope
Default timeout in milliseconds for all request scopes
onError
(error: Error, ctx: Context) => void
Custom error handler for scope disposal errors
Middleware Architecture
Root Scope Creation
A singleton root scope is created on first middleware invocation
State Injection
Request scope is stored in ctx.state.scope and root in ctx.state.rootScope
Request Processing
Each request gets a unique child scope from the root
Cleanup in Finally
Request scopes are disposed in a finally block after middleware chain completes
Helper Functions
getScope(ctx)
Retrieves the request-scoped scope from the Koa context:
import { getScope } from '@go-go-scope/adapter-koa'
app.use(async (ctx) => {
const scope = getScope(ctx)
const [err, data] = await scope.task(() => fetchData())
ctx.body = data
})
getRootScope(ctx)
Retrieves the root application scope:
import { getRootScope } from '@go-go-scope/adapter-koa'
app.use(async (ctx) => {
const rootScope = getRootScope(ctx)
// Access application-level scope
ctx.body = { status: 'ok' }
})
closeKoaScope()
Gracefully disposes the root scope:
import { closeKoaScope } from '@go-go-scope/adapter-koa'
process.on('SIGTERM', async () => {
await closeKoaScope()
server.close()
})
Usage Examples
REST API with Router
import Koa from 'koa'
import Router from '@koa/router'
import { koaGoGoScope } from '@go-go-scope/adapter-koa'
const app = new Koa()
const router = new Router()
app.use(koaGoGoScope())
router.get('/posts/:id', async (ctx) => {
const scope = ctx.state.scope
const [err, post] = await scope.task(
() => db.posts.findById(ctx.params.id)
)
if (err) {
ctx.status = 404
ctx.body = { error: 'Post not found' }
return
}
ctx.body = post
})
app.use(router.routes())
app.listen(3000)
Parallel Data Fetching
import Koa from 'koa'
import { koaGoGoScope } from '@go-go-scope/adapter-koa'
const app = new Koa()
app.use(koaGoGoScope())
app.use(async (ctx) => {
if (ctx.path !== '/dashboard') return
const scope = ctx.state.scope
const userId = ctx.query.userId
const [err, results] = await scope.parallel([
() => fetchProfile(userId),
() => fetchPosts(userId),
() => fetchFollowers(userId)
])
if (err) {
ctx.status = 500
ctx.body = { error: 'Failed to load dashboard' }
return
}
ctx.body = {
profile: results[0],
posts: results[1],
followers: results[2]
}
})
Custom Error Handler
import Koa from 'koa'
import { koaGoGoScope } from '@go-go-scope/adapter-koa'
const app = new Koa()
app.use(koaGoGoScope({
name: 'error-aware-api',
onError: (error, ctx) => {
console.error('Scope error:', error.message, 'Path:', ctx.path)
// Could send to error tracking service
}
}))
app.use(async (ctx) => {
const scope = ctx.state.scope
const [err, data] = await scope.task(
() => fetchData(),
{ timeout: 5000 }
)
if (err) {
ctx.status = 500
ctx.body = { error: err.message }
return
}
ctx.body = data
})
Streaming Response
import Koa from 'koa'
import { koaGoGoScope } from '@go-go-scope/adapter-koa'
import { PassThrough } from 'stream'
const app = new Koa()
app.use(koaGoGoScope())
app.use(async (ctx) => {
if (ctx.path !== '/stream-logs') return
const scope = ctx.state.scope
const channel = scope.channel({ buffer: 100 })
// Producer
scope.task(async ({ signal }) => {
for (let i = 0; i < 100; i++) {
if (signal.aborted) break
await channel.send(`Log line ${i}\n`)
await new Promise(r => setTimeout(r, 100))
}
channel.close()
})
// Stream to response
ctx.type = 'text/plain'
const stream = new PassThrough()
ctx.body = stream
for await (const line of channel) {
stream.write(line)
}
stream.end()
})
Middleware Composition
import Koa from 'koa'
import bodyParser from 'koa-bodyparser'
import cors from '@koa/cors'
import { koaGoGoScope } from '@go-go-scope/adapter-koa'
const app = new Koa()
// Apply middleware in order
app.use(cors())
app.use(bodyParser())
app.use(koaGoGoScope({ name: 'api' }))
app.use(async (ctx) => {
// Scope is available after koaGoGoScope middleware
const scope = ctx.state.scope
const [err, data] = await scope.task(() => fetchData())
ctx.body = data
})
Circuit Breaker
import Koa from 'koa'
import { koaGoGoScope } from '@go-go-scope/adapter-koa'
import { CircuitBreaker } from 'go-go-scope'
const breaker = new CircuitBreaker({
failureThreshold: 5,
resetTimeout: 30000
})
const app = new Koa()
app.use(koaGoGoScope())
app.use(async (ctx) => {
if (ctx.path !== '/external') return
const scope = ctx.state.scope
const [err, response] = await scope.task(async ({ signal }) => {
return breaker.execute(() =>
fetch('https://external-api.com/data', { signal })
)
})
if (err) {
if (breaker.state === 'open') {
ctx.status = 503
ctx.body = { error: 'Service unavailable' }
return
}
ctx.status = 500
ctx.body = { error: err.message }
return
}
ctx.body = await response.json()
})
Type Augmentation
The adapter augments Koa’s DefaultState interface:
declare module 'koa' {
interface DefaultState {
scope: Scope<Record<string, unknown>>
rootScope: Scope<Record<string, unknown>>
}
}
This provides full type safety:
app.use(async (ctx) => {
// TypeScript knows ctx.state.scope is Scope
const scope = ctx.state.scope
const [err, data] = await scope.task(() => fetchData())
ctx.body = data
})
Error Handling
The middleware handles errors during scope disposal:
finally {
await requestScope[Symbol.asyncDispose]().catch((err) => {
if (onError) {
onError(err as Error, ctx)
}
})
}
Graceful Shutdown
import { closeKoaScope } from '@go-go-scope/adapter-koa'
const server = app.listen(3000)
const shutdown = async () => {
console.log('Shutting down gracefully...')
// Dispose root scope (cancels all tasks)
await closeKoaScope()
// Close HTTP server
server.close(() => {
console.log('Server closed')
process.exit(0)
})
}
process.on('SIGTERM', shutdown)
process.on('SIGINT', shutdown)
Best Practices
Use state for scope access
Always access scopes via ctx.state.scope or use the helper function getScope(ctx).
Register koaGoGoScope() before route handlers to ensure scopes are available.
Handle errors in middleware
Set appropriate HTTP status codes when tasks fail.
Implement graceful shutdown
Always call closeKoaScope() before closing the HTTP server.
Express Adapter
Express middleware integration
Hapi Adapter
Hapi plugin integration
Fastify Adapter
Fastify plugin integration
Core API
Core go-go-scope concepts