Allow multiple handlers with same path in something like guard
Author: bogeychanCreated Jul 27, 2024Updated Sep 12, 2026
Labelsenhancementfeature request
What is the problem this feature would solve?
Webhooks send POST requests to the same endpoint (based on event-literal). Someone on discord wanna separate this logic into different functions based on event.
Pseudo code:
const customerCreated = new Elysia({ name: "created" })
.if(({ body }) => body.event === "customer.created")
.post("/", () => console.log("customer created!"));
const customerUpdated = new Elysia({ name: "updated" })
.if(({ body }) => body.event === "customer.updated")
.post("/", () => console.log("customer updated!"));
const stripeWebhook = new Elysia().use(customerCreated).use(customerUpdated);What is the feature you are proposing to solve the problem?
Allow something like this to work with the same path (/):
const webhooks = new Elysia({ name: "webhooks" })
.guard(
{
body: t.Object({
event: t.Literal("customer.created"),
propCreated: t.String(),
}),
},
(app) => app.post("/", () => "cr")
)
.guard(
{
body: t.Object({
event: t.Literal("customer.updated"),
propUpdated: t.String(),
}),
},
(app) => app.post("/", () => "up")
);
new Elysia().use(webhooks).listen(8080);At the moment it throws a validation error:
Click to expand{
"type": "validation",
"on": "body",
"summary": "Property 'propUpdated' is missing",
"property": "/propUpdated",
"message": "Required property",
"expected": {
"event": "customer.updated",
"propUpdated": ""
},
"found": {
"event": "customer.created"
},
"errors": [
{
"type": 45,
"schema": {
"type": "string"
},
"path": "/propUpdated",
"message": "Required property",
"summary": "Property 'propUpdated' is missing"
},
{
"summary": "Expected 'customer.updated'",
"type": 32,
"schema": {
"const": "customer.updated",
"type": "string"
},
"path": "/event",
"value": "customer.created",
"message": "Expected 'customer.updated'"
},
{
"type": 54,
"schema": {
"type": "string"
},
"path": "/propUpdated",
"message": "Expected string",
"summary": "Expected property 'propUpdated' to be string but found: undefined"
}
]
}If i send this body:
{
"event": "customer.created",
"propCreated": "1"
}What alternatives have you considered?
I considered this, but it has a lot of unnecessary code:
This is not easy for newbies and i've to do isCustomerUpdated , ... checks because of typescript type guard limitation with nested unions.
import {
t,
Elysia,
type Static,
type Context,
type TSchema,
type InputSchema,
} from "elysia";
const customerCreated = t.Object({
event: t.Literal("customer.created"),
propCreated: t.String(),
});
type CustomerCreatedContext = StaticContextExclude<
typeof customerWebhooks,
Static<typeof customerUpdated>
>;
const customerUpdated = t.Object({
event: t.Literal("customer.updated"),
propUpdated: t.String(),
});
type CustomerUpdatedContext = StaticContextExclude<
typeof customerWebhooks,
Static<typeof customerCreated>
>;
const customerWebhooks = {
body: t.Union([customerCreated, customerUpdated]),
} satisfies InputSchema;
type StaticContext<T> = Context<{
[Key in keyof T]: T[Key] extends TSchema ? Static<T[Key]> : T[Key];
}>;
type StaticContextExclude<T, E> = Context<{
[Key in keyof T]: T[Key] extends TSchema
? Exclude<Static<T[Key]>, E>
: Exclude<T[Key], E>;
}>;
function handleCustomerCreated({ body }: CustomerCreatedContext) {
return body.propCreated;
}
function handleCustomerUpdated({ body }: CustomerUpdatedContext) {
return body.propUpdated;
}
function isCustomerCreated(
ctx: StaticContext<typeof customerWebhooks>
): ctx is CustomerCreatedContext {
return ctx.body.event === "customer.created";
}
function isCustomerUpdated(
ctx: StaticContext<typeof customerWebhooks>
): ctx is CustomerUpdatedContext {
return ctx.body.event === "customer.updated";
}
const webhooks = new Elysia({ name: "webhooks" }).post(
"/",
(ctx) => {
if (isCustomerCreated(ctx)) {
return handleCustomerCreated(ctx);
} else if (isCustomerUpdated(ctx)) {
return handleCustomerUpdated(ctx);
}
},
customerWebhooks
);
new Elysia().use(webhooks).listen(8080);Source: elysiajs/elysia