express-cargo is a middleware library for Express.js that makes handling request data easier and more type-safe. It provides class-based decorators and binding features to simplify complex request parsing and validation.
npm install express-cargo reflect-metadataexpress-cargo uses TypeScript decorators and runtime type metadata. To use it properly, you need to install TypeScript and enable a few compiler options.
npm install -D typescript
Add the following settings to your tsconfig.json:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}import express from 'express'
import { Body, bindingCargo, getCargo, min, Header, Params } from 'express-cargo'
const app = express()
app.use(express.json())
class RequestExample {
@Body()
name!: string
@Body()
@Min(0)
age!: number
@Params('id')
id!: number
@Header()
authorization!: string
}
app.post('/:id', bindingCargo(RequestExample), (req, res) => {
const data = getCargo<RequestExample>(req)
// write your code with bound data
})
app.listen(3000)Full guide and API reference:
👉 express-cargo Documentation
- Class-based request parsing: Automatically bind request data (body, query, params, etc.) using decorators
- Type safety: Fully compatible with TypeScript
- Easy middleware integration: Seamlessly works with existing Express middleware
| Decorator | Description | Example |
|---|---|---|
@Body() |
Binds a field from req.body |
@Body() name: string |
@Query() |
Binds a field from req.query |
@Query() page: number |
@Params() |
Binds a field from req.params |
@Params() id: string |
@Uri() |
alias of @params() | @Uri() id: string |
@Header() |
Binds a field from req.headers |
@Header() token: string |
@Session() |
Binds a field from req.session |
@Session() userId: string |
| Decorator | Description | Example |
|---|---|---|
@Optional() |
Skip validation when value is null or undefined. | @Optional() value?: number |
@Min(minimum: number) |
Number must be greater than or equal to minimum. |
@Min(18) age!: number |
@Max(maximum: number) |
Number must be less than or equal to maximum. |
@Max(100) score!: number |
@Range(min: number, max: number) |
Number must be between min and max (inclusive). |
@Range(1, 5) rating!: number |
@Prefix(prefixText: string) |
String must start with prefixText. |
@Prefix('IMG_') fileName!: string |
@Suffix(suffixText: string) |
String must end with suffixText. |
@Suffix('.jpg') fileName!: string |
@Length(value: number) |
String length must be exactly value. |
@Length(6) otp!: string |
@MinLength(min: number) |
String length must be greater than or equal to min. |
@MinLength(8) password!: string |
@MaxLength(max: number) |
String length must be less than or equal to max. |
@MaxLength(20) username!: string |
@Equal(value: any) |
Value must be strictly equal to value. |
@Equal('production') env!: string |
@NotEqual(value: any) |
Value must not be equal to value. |
@NotEqual('admin') role!: string |
@IsTrue() |
Value must be true. |
@IsTrue() acceptedTerms!: boolean |
@IsFalse() |
Value must be false. |
@IsFalse() blocked!: boolean |
@OneOf(options: readonly any[]) |
Value must be one of options. |
@OneOf(['credit','debit'] as const) method!: 'credit' | 'debit' |
@Validate(validateFn, message?) |
Custom validation function. | @Validate(v => typeof v === 'string' && v.includes('@'), 'invalid email') email!: string |
@Regexp(pattern: RegExp, message?) |
String must match the given regular expression. | @Regexp(/^[0-9]+$/, 'digits only') phone!: string |
@Email() |
String must be email format. | @Email() email!: string |
@Uuid(version?, message?) |
Validates that the field is a valid UUID, optionally restricted to a specific version (v1, v3, v4, or v5). | @Uuid('v4') requestId!: string |
@Alphanumeric(message?: string) |
Validates that the field contains alphanumeric characters (A-Z, a-z, 0-9) only. | @Alphanumeric() productCode!: string |
@With(fieldName: string) |
Validates that if the decorated field has a value, the specified target field (fieldName) must also have a value, establishing a mandatory dependency. | @With('price') discountRate?: number |
@Without(fieldName: string) |
Validates that if the decorated field has a value, the specified target field (fieldName) must NOT have a value, establishing a mutually exclusive relationship. | @Without('isGuest') password?: string |
| Decorator | Description | Example |
|---|---|---|
@Transform(transformer) |
Transform the parsed value | @Transform(v => v.trim()) name!: string |
@Request(transformer) |
Extract value from Express Request object | @Request(req => req.ip) clientIp!: string |
@Virtual(transformer) |
Compute value from other fields | @Virtual(obj => obj.firstName + ' ' + obj.lastName) fullName!: string |
| Decorator | Description | Example |
|---|---|---|
@Default(value) |
Set default value when field is missing | @Default(0) count!: number |
@Array(elementType) |
Specify array element type | @Array(String) tags!: string[] |
import { setCargoErrorHandler, CargoValidationError } from 'express-cargo'
// Custom error handler
setCargoErrorHandler((err, req, res, next) => {
if (err instanceof CargoValidationError) {
res.status(400).json({
error: 'Validation failed',
details: err.errors.map(e => ({
field: e.field,
message: e.name
}))
})
} else {
next(err)
}
})MIT