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.attachRoutesdefaults tofalse; attachrouter.routes()androuter.allowedMethods()yourself unless you set it totrue.
Set diagnostics: true temporarily to print registered controller and route paths during startup.
Decorated controllers are not discovered
Prefer passing controller classes directly:
controllers: [UserController, HealthController]
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:
bodyParser: {
multipart: true,
formidable: {
maxFileSize: 5 * 1024 * 1024,
maxFiles: 2,
},
}
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},
});
await bootstrapControllers({
bodyParser: false,
controllers: [UploadController],
});
@Controller('/uploads')
class UploadController {
@Flow([upload.single('image')])
@Post('/')
upload(@File() file: {originalname: string; size: number}) {
return {name: file.originalname, size: file.size};
}
}
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.
A dependency injection container cannot construct controllers
Use the controllerFactory bootstrap option to resolve controller instances. It runs for every request and receives both the controller class and Koa context. Avoid a process-wide mutable request scope; create and dispose request scopes in middleware.
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.