Troubleshooting
A route returns 404
Check the complete generated path:
basePathis prefixed first.- Versioning adds
/v1by default. @Controllerand endpoint paths are appended after the version.- Amala 13 attaches routes by default. If
attachRoutes: falseis set, mountrouter.routes()androuter.allowedMethods()manually.
Set diagnostics: true temporarily to print registered controller and route paths during startup.
Middleware added after bootstrap does not run
Amala 13 attaches controller routes during bootstrap. Koa middleware added afterward may sit behind a matching route handler in the stack.
Register middleware on an existing Koa app before bootstrap, pass global middleware through flow, or set attachRoutes: false and mount the returned router after the application middleware:
const {app, router} = await bootstrapControllers({
attachRoutes: false,
controllers: [UserController],
});
app.use(applicationMiddleware);
app.use(router.routes());
app.use(router.allowedMethods());
Decorated controllers are not discovered
Prefer passing controller classes directly:
const {app} = await bootstrapControllers({
controllers: [UserController, HealthController],
diagnostics: true,
});
app.listen(3000);
When using a glob, make it absolute and match the files produced in the environment. A development glob ending in .ts will not find compiled .js files in production. Controller modules execute when they are loaded, so globs must come only from trusted configuration.
Validation does not run
Validated inputs must be classes with class-validator decorators. TypeScript interfaces are erased at runtime.
Confirm these compiler options:
{
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
If a bundler strips decorator metadata, compile the decorated code with tsc or a toolchain that explicitly preserves the same metadata.
Cannot find namespace 'ValidatorJS'
This usually indicates incompatible or duplicated class-validator, validator, or @types/validator versions. Start from the locked dependency tree, remove stale install artifacts, and reinstall with the repository's package manager. Avoid adding a second copy of class-validator to work around the error.
The request body is undefined
Amala installs koa-body unless bodyParser is false. Verify that:
- the request uses
POST,PUT, orPATCH, or the method appears inparsedMethods; - its
Content-Typematches JSON, form, text, or multipart input; - its size is below the configured limit; and
- a custom parser runs before the router when
bodyParser: falseis used.
File uploads are missing
Multipart parsing must be enabled explicitly if your application disabled it.
Use @File() or @Req() to access uploads. koa-body places uploads in ctx.request.files. For Multer, use the Koa adapter and disable Amala's parser so only one middleware consumes the stream:
import multer from '@koa/multer';
const upload = multer({
storage: multer.memoryStorage(),
limits: {fileSize: 5 * 1024 * 1024},
});
@Controller('/uploads')
class UploadController {
@Flow([upload.single('image')])
@Post('/')
upload(@File() file: {originalname: string; size: number}) {
return {name: file.originalname, size: file.size};
}
}
const {app} = await bootstrapControllers({
bodyParser: false,
controllers: [UploadController],
});
app.listen(3000);
Use @koa/multer, not Express's multer middleware directly. A single upload is available from ctx.request.file; field and array uploads use ctx.request.files. Store files explicitly in the handler or a service—parsing an upload does not persist it automatically.
Swagger cannot load the OpenAPI document
With basePath: '/api', the default routes are /api/docs and /api/swagger. Omit publicURL for same-origin access, or set it to the externally reachable API origin. Do not point a public Swagger page at an internal-only hostname.
Generated OpenAPI server URLs contain basePath and the active version. Operation paths are relative to those server URLs, so clients resolve /users/:id as /api/v1/users/:id without repeating /api.
Multiple Amala apps affect each other
Controller decorator metadata is process-wide. Two apps in one Node.js process can see the same registered controller names and metadata. Run independent or mutually untrusted APIs in separate processes.
Custom controller construction fails
Use the controllerFactory bootstrap option only when the default per-request new ControllerClass(ctx) behavior does not fit the application. It runs for every request and receives both the controller class and the typed Koa context. Amala does not provide a container or manage application service lifecycles.
The documentation site does not build
The current Docusaurus site requires Node.js 20 or newer. From docs/, run npm ci before npm run build so the lockfile and toolchain stay aligned.