Fillable and Guarded Fields
Alapa provides robust mechanisms for managing mass assignment through fillable
and guarded
fields.
These features help prevent security vulnerabilities while maintaining flexibility in your data operations.
Important Security Noteβ
By default, Alapa silently discards attributes that aren't in fillable fields during mass assignment.
This can lead to confusion in development, as it may appear that certain fields aren't updating. To surface these issues:
import { ENV } from "alapa";
export const database: DatabaseConfiguration = {
preventSilentlyDiscardingAttributes: ENV == "development",
};
This setting makes the system throw errors in development when attempting to assign non-fillable fields, helping you catch configuration mistakes early.
Enable preventSilentlyDiscardingAttributes
in development to catch assignment issues early, but keep it disabled in production for security.
Flexible Ways to Define Fillable Fieldsβ
Alapa offers multiple declarative patterns for defining fillable fields:
fillableFields
property (static declaration)@FillableField()
decorator (property-level)setFillableFields()
method (dynamic declaration)
These approaches can be used independently or combinedβAlapa automatically merges all definitions while eliminating duplicates.
Static Declaration with fillableFields
β
The fillableFields
property provides a straightforward way to declare mass-assignable attributes:
import { Model, ModelFillableFields, TableModel, Column } from "alapa";
@TableModel()
export class Users extends Model {
@Column()
firstName: string;
@Column()
lastName: string;
protected fillableFields: ModelFillableFields<Users>[] = [
"firstName",
"lastName",
];
//protected fillableFields: ModelFillableFields<Users>[] = ["*"];
}
Using "*"
to make all fields fillable is strongly discouraged, as it may expose your model to mass assignment vulnerabilities.
Implementing Mass Assignmentβ
After defining fillable fields, use the Model.fill()
or Model.create()
method for safe population:
import { Router, Request, Response } from "alapa";
import { Users } from "../models/user";
const userRoutes = new Router();
userRoutes.get("/", async (req: Request, res: Response) => {
const user = new Users();
user.fill({
firstName: "John",
lastName: "Doe",
});
if (user.isDirty()) {
await user.save();
}
});
Property-Level Declaration with @FillableField()
β
For more granular control, decorate individual properties:
import { Model, Column, TableModel, FillableField } from "alapa";
@TableModel()
export class Users extends Model {
@FillableField()
@Column()
firstName: string;
@FillableField()
@Column()
lastName: string;
@Column()
balance: number;
@Column()
isActive: boolean;
}
Dynamic Configuration with setFillableFields()
β
When you need to define fillable fields based on complex logic, overriding the setFillableFields() method is recommended. This approach gives you full flexibility to compute fields dynamically.
import { Model, Column, TableModel, ModelFillableFields } from "alapa";
@TableModel()
export class Users extends Model {
@Column()
firstName: string;
@Column()
lastName: string;
protected setFillableFields(): ModelFillableFields<Users>[] {
return ["firstName", "lastName"];
}
}
Conditional Field Assignmentβ
Business Logic-Based Fillable Fieldsβ
import { Model, Column, TableModel, ModelFillableFields } from "alapa";
@TableModel()
export class Users extends Model {
@Column()
firstName: string;
@Column()
lastName: string;
@Column()
balance: number;
protected fillableFields: ModelFillableFields<Users>[] = [
"firstName",
"lastName",
{ balance: (user: Users) => loginUser.role === "admin" },
];
}
Decorator-Based Conditionsβ
import { Model, Column, TableModel, FillableField } from "alapa";
@TableModel()
export class Users extends Model {
@FillableField((user: Users) => loginUser.role === "admin")
@Column()
balance: number;
}
loginUser
should reference your application's current authentication context.
Field Management Utilitiesβ
Model.updateFillableFields()
β
Dynamically modify fillable fields at runtime:
import { Router, Request, Response } from "alapa";
import { Users } from "../models/user";
const userRoutes = new Router();
userRoutes.get("/", async (req: Request, res: Response) => {
const user = new Users();
user.updateFillableFields(["firstName", "lastName"], true);
});
export default userRoutes;
Parameter | Type | Description |
---|---|---|
fields | ModelFillableFieldsMethod<Users>[] | Fields to add/update |
override | boolean | When true, replaces all existing definitions |
Model.getFillableFields()
β
Retrieve current fillable field definitions:
import { Router, Request, Response } from "alapa";
import { Users } from "../models/user";
const userRoutes = new Router();
userRoutes.get("/", async (req: Request, res: Response) => {
const user = new Users();
const fillableFields = user.getFillableFields();
console.log(fillableFields); // [firstName, lastName]
});
export default userRoutes;
Guarded Fields Configurationβ
Guarded fields take the opposite approach of fillable fields, specifying which attributes cannot be mass-assigned.
Key characteristics:
- No wildcard (
*
) support - Useful when you have just a few sensitive fields to protect
1. Static Declaration with guardedFields
β
import { Model, ModelGuardedFields, TableModel, Column } from "alapa";
@TableModel()
export class Users extends Model {
@Column()
firstName: string;
@Column()
lastName: string;
@Column()
publicKey: number;
@Column()
secretKey: string;
protected guardedFields: ModelGuardedFields<Users>[] = [
"publicKey",
"secretKey",
];
}
2. Property-Level Declaration with @GuardedField()
β
import { Model, GuardedField, TableModel, Column } from "alapa";
@TableModel()
export class Users extends Model {
@Column()
firstName: string;
@Column()
lastName: string;
@GuardedField()
@Column()
apiToken: string;
}
3. Dynamic Configuration with setGuardedFields()
β
import { Model, ModelGuardedFields, TableModel, Column } from "alapa";
@TableModel()
export class Users extends Model {
@Column()
firstName: string;
@Column()
lastName: string;
@Column()
apiToken: string;
@Column()
passwordResetToken: string;
protected setGuardedFields(): ModelGuardedFields<Users>[] {
return ["apiToken", "passwordResetToken"];
}
}
4. Conditional Guarded Fieldsβ
import { Model, ModelGuardedFields, TableModel, Column } from "alapa";
@TableModel()
export class Users extends Model {
@Column()
firstName: string;
@Column()
lastName: string;
@Column()
ssn: number;
protected guardedFields: ModelGuardedFields<Users>[] = [
{ ssn: (user) => !user.hasFinancialAccess() },
];
}
Guarded Field Management Utilitiesβ
Model.updateGuardedFields;β
import { Router, Request, Response } from "alapa";
import { Users } from "../models/user";
const userRoutes = new Router();
userRoutes.get("/", async (req: Request, res: Response) => {
const user = new Users();
user.updateGuardedFields(["passwordResetToken"], true);
});
export default userRoutes;
Model.getGuardedFieldsβ
import { Router, Request, Response } from "alapa";
import { Users } from "../models/user";
const userRoutes = new Router();
userRoutes.get("/", async (req: Request, res: Response) => {
const user = new Users();
const guardedFields = user.getGuardedFields();
console.log(guardedFields); // [passwordResetToken]
});
export default userRoutes;
When both fillable
and guarded
fields are defined, the fillable
configuration always takes the highest priority, and the guarded
declaration will be ignored.
Unified Field Resolution & Best Practicesβ
Alapa intelligently merges all field definitions with the following rules:
- Definitions from
fillableFields
,@FillableField()
, andsetFillableFields()
are combined. - Guarded fields from all sources are merged similarly.
- Fillable fields override guarded fields in case of conflict.
Recommended Best Practicesβ
- β
Prefer
fillable
fields in most cases - π‘οΈ Use
guarded
when you few sensitive fields to protect - π« Never define both on the same model
- π§ͺ Enable
preventSilentlyDiscardingAttributes
in development - π Regularly audit field-level security
By properly implementing these patterns, you ensure secure handling of model data while maintaining the flexibility needed for complex business requirements.