Middlewares
Middlewares allow you to reuse logic by handling tasks like validation, access control, or response formatting. They offer several features:
- Passing data to other middlewares.
- Preventing further execution based on conditions.
- Sending responses, so you don't need to handle every case manually.
Note: Schema validators are also treated as middlewares.
Basic
builder
.middleware(async () => {
const randomValue = Math.random();
if (randomValue > 0.5) return { next: true, randomValue };
return { next: false, statusCode: 500, message: "Unlucky" };
})
.get(async ({ data }) => {
return success({
message: `You won with ${data.randomValue}`,
});
});
In this example:
- If the middleware returns
{ next: true }
, the execution proceeds to the next handler. You can also type-safely add data, as shown withrandomValue
indata.randomValue
- If the middleware returns
{ next: false }
, it sends an immediate response, which requires astatusCode
. It's recommended to include aresult: 'some-error' as const
for having a proper discriminated union in the response.
Middleware Chaining
Some middlewares need to use data coming from schema validators. In order to use the data from the schema validation you have to keep the middleware function in the builder code. This, however, prevents you to just move the middleware function out of the builder. To solve this, Cuple introduces a wrapper called link, and you can connect links with each other.
.buildLink()
Creates a link from a build chain. Use this instead of the final middleware (get, post, ...)..chain(fooLink).chain(barLink)
to bring multiple links into the build chain
Example:
const authLink = builder
.headersSchema(
z.object({
authorization: z.string(),
})
)
.middleware(async ({ data }) => {
if (data.headers.authorization === "foo")
return {
next: true,
};
return {
next: false,
statusCode: 401 as const,
};
})
.buildLink();
const routes = {
getAuthedWelcomeMessage: builder
.chain(authLink)
.get(async () => {
return success({ message: "hi" });
}),
};
Dependent chain links (Since 1.0.10)
Middlewares can depend on the output of previous middlewares. For example, a role-check middleware might depend on the result of an authentication middleware.
Example:
const authLink = builder
.middleware(async () => ({ next: true, auth: { userId: 12345 } }))
.buildLink();
const adminRoleLink = builder
.expectChain<typeof authLink>()
.middleware(async ({ data }) => {
const user = await getUser(data.auth.userId)
if(user.role !== 'admin') {
return { next: false, statusCode: 403, message: "This is admin only." }
}
return { next: true, user }
})
.buildLink();
return {
someRoute: builder
.chain(authLink)
.chain(adminRoleLink)
.get(async ({ data }) => {
return success({
message: `You have access! Good ${data.user.name}`
});
}),
};
In this example:
adminRoleLink
depends on the output ofauthLink
.
Dealing with service dependencies
In more complex applications, middleware may need additional services like a database or external APIs. To keep the code maintainable and testable, it's best to inject these dependencies rather than import them directly. You can achieve this by creating middleware link factories.
Example:
function createAuthLink(builder: CupleBuilder) {
return builder
.middleware(async () => ({ next: true, auth: { userId: 12345 } }))
.buildLink()
}
type AuthLink = ReturnType<typeof createAuthLink>
function createAdminRoleLink(builder: CupleBuilder, userRepository: UserRepository) {
return builder
.expectChain<AuthLink>()
.middleware(async ({ data }) => {
const user = await getUser(data.auth.userId)
if(user.role !== 'admin') {
return { next: false, statusCode: 403, message: "Admins only." }
}
return { next: true, user }
})
.buildLink()
}
function createAdminModule(builder: CupleBuilder, userRepository: UserRepository) {
const authLink = createAuthLink(builder);
const adminRoleLink = createAdminRoleLink(builder, userRepository);
return {
someRoute: builder
.chain(authLink)
.chain(adminRoleLink)
.get(async ({ data }) => {
return success({
message: `You have access! Good ${data.user.name}`
});
}),
};
}
- note:
CupleBuilder
is an exported type from theindex.ts
file