| name | koapp-controller |
| description | Organize route handlers into reusable classes by extending the @axiosleo/koapp Controller base class. Use when grouping related handlers, reusing response helpers (this.success/failed/result/response/error/log), sharing state across handlers, or scaffolding service modules for a koapp project. |
@axiosleo/koapp Controller
Source: src/controller.js.
Controller is a thin base class that mirrors the
response helpers as instance methods. Extend
it to keep related handlers together and share auxiliary state.
Import
const { Controller } = require('@axiosleo/koapp');
Instance API
| Method | Equivalent function | Notes |
|---|
this.success(data?, headers?) | success(data, headers) | 200 OK JSON envelope |
this.failed(data?, code?, status?, headers?) | failed(data, code, status, headers) | Non-2xx JSON envelope |
this.result(data, status?, headers?) | result(data, status, headers) | Raw body pass-through |
this.response(data, code, status?, headers?) | response(data, code, status, headers) | Full control envelope |
this.error(status, msg, headers?) | error(status, msg, headers) | Shortcut error envelope |
this.log(...data) | debug.log from @axiosleo/cli-tool | Conditional logging |
All this.success / failed / result / response / error throw, same as the
functional helpers. See koapp-response for envelope details.
Basic usage
const { Controller, Router } = require('@axiosleo/koapp');
class HealthController extends Controller {
async ping(context) {
this.log('ping from', context.request_id);
this.success({ pong: true });
}
async version() {
this.success({ version: require('../package.json').version });
}
}
const health = new HealthController();
const router = new Router('/health');
router.get('/ping', (ctx) => health.ping(ctx));
router.get('/version', () => health.version());
Why use Controller over plain functions?
- Shared state: load DB handles, caches, or config once in the
constructor and reuse across methods.
- Inheritance: define a
BaseController with common middlewares or
helpers, then extend it per module (the init scaffolding template at
assets/tmpl/services/src/modules/controller.ts.tmpl does this).
- Testability: easier to mock a controller instance than free-floating
functions.
Binding handlers to routes
Controller methods expect a context argument and may use this, so bind
them explicitly when registering with the router:
router.get('/users/{:id}', (ctx) => controller.find(ctx));
router.post('/users', (ctx) => controller.create(ctx));
class UsersController extends Controller {
find = async (ctx) => {
const id = Number(ctx.params.id);
this.success({ id });
};
}
Pure wrappers (ctx) => controller.method(ctx) preserve this and avoid
.bind clutter.
Error handling
Since this.success / failed / ... throw, any code after them does not
run. The workflow catches the throw and writes the response. Keep try /
catch only where you intend to shape a different error:
async create(context) {
try {
const user = await this.db.users.insert(context.body);
this.success({ id: user.id });
} catch (err) {
if (err.code === 'DUP_ENTRY') {
this.failed({ email: context.body.email }, '409;Email Already Registered', 409);
}
throw err;
}
}
Logging
this.log is wired to debug.log from @axiosleo/cli-tool. Enable it by
running with DEBUG=1 (or the cli-tool equivalent), or by setting
debug: true on the application.
this.log('loading user', context.params.id);
Composing with validators
Validation stays on the router:
router.push('post', '/users', (ctx) => controller.create(ctx), {
body: {
rules: {
name: 'required|string',
email: 'required|email'
}
}
});
The validator runs before the handler, so by the time controller.create
executes you already have a validated context.body.
Generator output
Running npx @axiosleo/koapp gen -d ./meta -o ./services/src/modules
produces .controller.ts files that extend a project-local
BaseController. That BaseController typically extends Controller and
adds database handles:
import { Controller } from '@axiosleo/koapp';
import { mainDB } from '../services/db';
export class BaseController extends Controller {
protected mainDB = mainDB;
}
Common pitfalls
- Forgetting to pass
context into instance methods: router.get('/x', controller.handler) loses this when the router calls the method as a free function. Use arrow wrappers.
- Returning
this.success(...) from a handler is redundant - the throw short-circuits execution.
- Controllers are long-lived across requests; never stash per-request data on
this. Use context or local variables instead.
See also
- Response helper details: koapp-response
- Grouping controllers under a router tree: koapp-router
- Concrete controller recipes: examples.md