Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions examples/express/valibot/express.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import express from "express";
import { asAsync } from "../../../src/express";
import { pathMap } from "./spec";
import { ToHandlers, typed } from "../../../src/express/valibot";

const emptyMiddleware = (
req: express.Request,
res: express.Response,
next: express.NextFunction,
) => next();
type Handlers = ToHandlers<typeof pathMap>;
const newApp = () => {
const app = express();
app.use(express.json());
// `typed` method is equivalent to below 2 lines code:
// ```
// // validatorMiddleware allows to use res.locals.validate method
// app.use(validatorMiddleware(pathMap));
// // wApp is same as app, but with additional common information
// const wApp = app as TRouter<typeof pathMap>;
// ```
const wApp = asAsync(typed(pathMap, app));
wApp.get("/users", emptyMiddleware, (req, res) => {
{
// @ts-expect-error params is not defined because pathMap["/users"]["get"].params is not defined
res.locals.validate(req).params();
}

// validate method is available in res.locals
// validate(req).query() is equals to pathMap["/users"]["get"].query.safeParse(req.query)
const { data, error } = res.locals.validate(req).query();
if (data !== undefined) {
// res.status(200).json() accepts only the response schema defined in pathMap["/users"]["get"].res["200"]
res.status(200).json({ userNames: [`page${data.page}#user1`] });
} else {
// res.status(400).json() accepts only the response schema defined in pathMap["/users"]["get"].res["400"]
res.status(400).json({ errorMessage: error.toString() });
}
});
wApp.post("/users", (req, res) => {
// validate(req).body() is equals to pathMap["/users"]["post"].body.safeParse(req.body)
const { data, error } = res.locals.validate(req).body();
{
// Request header also can be validated
res.locals.validate(req).headers();
}
if (data !== undefined) {
// res.status(200).json() accepts only the response schema defined in pathMap["/users"]["post"].res["200"]
res.status(200).json({ userId: data.userName + "#0" });
} else {
// res.status(400).json() accepts only the response schema defined in pathMap["/users"]["post"].res["400"]
res.status(400).json({ errorMessage: error.toString() });
}
});

const getUserHandler: Handlers["/users/:userId"]["get"] = (req, res) => {
const { data: params, error } = res.locals.validate(req).params();

if (params !== undefined) {
// res.status(200).json() accepts only the response schema defined in pathMap["/users/:userId"]["get"].res["200"]
res.status(200).json({ userName: "user#" + params.userId });
} else {
// res.status(400).json() accepts only the response schema defined in pathMap["/users/:userId"]["get"].res["400"]
res.status(400).json({ errorMessage: error.toString() });
}
};
wApp.get("/users/:userId", getUserHandler);

return app;
};

const main = async () => {
const app = newApp();
const port = 3000;
app.listen(port, () => {
console.log(`Example app listening on port ${port}`);
});
};

main();
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { PathMap } from "./spec";
import JSONT from "../../src/json";
import { unreachable } from "../../src/utils";
import type FetchT from "../../src/fetch";
import JSONT from "../../../src/json";
import { unreachable } from "../../../src/utils";
import type FetchT from "../../../src/fetch";

const fetchT = fetch as FetchT<typeof origin, PathMap>;
const origin = "http://localhost:3000";
Expand Down
40 changes: 40 additions & 0 deletions examples/express/valibot/spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as v from "valibot";
import { ToApiEndpoints, ValibotApiEndpoints } from "../../../src/valibot";

const JsonHeader = v.object({
"Content-Type": v.literal("application/json"),
});
export const pathMap = {
"/users": {
get: {
query: v.object({
page: v.string(),
}),
resBody: {
200: v.object({ userNames: v.array(v.string()) }),
400: v.object({ errorMessage: v.string() }),
},
},
post: {
headers: JsonHeader,
resHeaders: JsonHeader,
resBody: {
200: v.object({ userId: v.string() }),
400: v.object({ errorMessage: v.string() }),
},
body: v.object({
userName: v.string(),
}),
},
},
"/users/:userId": {
get: {
params: v.object({ userId: v.string() }),
resBody: {
200: v.object({ userName: v.string() }),
400: v.object({ errorMessage: v.string() }),
},
},
},
} satisfies ValibotApiEndpoints;
export type PathMap = ToApiEndpoints<typeof pathMap>;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import express from "express";
import { asAsync } from "../../src/express";
import { asAsync } from "../../../src/express";
import { pathMap } from "./spec";
import { ToHandlers, typed } from "../../src/express/zod";
import { ToHandlers, typed } from "../../../src/express/zod";

const emptyMiddleware = (
req: express.Request,
Expand Down
93 changes: 93 additions & 0 deletions examples/express/zod/fetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import type { PathMap } from "./spec";
import JSONT from "../../../src/json";
import { unreachable } from "../../../src/utils";
import type FetchT from "../../../src/fetch";

const fetchT = fetch as FetchT<typeof origin, PathMap>;
const origin = "http://localhost:3000";
const headers = { "Content-Type": "application/json" } as const;
const JSONT = JSON as JSONT;

const main = async () => {
{
const path = `${origin}/users?page=1`;
const method = "get";
const res = await fetchT(path, { method });
switch (res.status) {
case 200: {
// r is the response schema defined in pathMap["/users"]["get"].res["200"]
const r = await res.json();
console.log(`${path}:${method} => ${r.userNames}`);
break;
}
case 400: {
// e is the response schema defined in pathMap["/users"]["get"].res["400"]
const e = await res.json();
console.log(`${path}:${method} => ${e.errorMessage}`);
break;
}
default:
unreachable(res);
}
}
{
// case-insensitive method example
await fetchT(`${origin}/users?page=1`, { method: "GET" });
}
{
// query parameter example
// TODO: Add common information for query parameter
const path = `${origin}/users?page=1`;
const method = "get";
const res = await fetchT(path, { method });
if (res.ok) {
// r is the response schema defined in pathMap["/users"]["get"].res["20X"]
const r = await res.json();
console.log(`${path}:${method} => ${r.userNames}`);
} else {
// e is the response schema defined in pathMap["/users"]["get"].res other than "20X"
const e = await res.json();
console.log(`${path}:${method} => ${e.errorMessage}`);
}
}

{
const path = `${origin}/users`;
const method = "post";
const res = await fetchT(path, {
method,
headers,
// body is the request schema defined in pathMap["/users"]["post"].body
// stringify is same as JSON.stringify but with common information
body: JSONT.stringify({ userName: "user1" }),
});
if (res.ok) {
// r is the response schema defined in pathMap["/users"]["post"].res["20X"]
const r = await res.json();
console.log(`${path}:${method} => ${r.userId}`);
} else {
// e is the response schema defined in pathMap["/users"]["post"].res other than "20X"
const e = await res.json();
console.log(`${path}:${method} => ${e.errorMessage}`);
}
}

{
// path parameter example
// "/users/:userId" accepts `/users/${string}` pattern
const path = `${origin}/users/1`;
const method = "get";
const res = await fetchT(path, { method });
if (res.ok) {
// r is the response schema defined in pathMap["/users/:userId"]["get"].res["20X"]
const r = await res.json();
console.log(`${path}:${method} => ${r.userName}`);
} else {
// e is the response schema defined in pathMap["/users/:userId"]["get"].res other than "20X"
const e = await res.json();
console.log(`${path}:${method} => ${e.errorMessage}`);
}
}
};

main();
2 changes: 1 addition & 1 deletion examples/express/spec.ts → examples/express/zod/spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { z } from "zod";
import { ToApiEndpoints, ZodApiEndpoints } from "../../src";
import { ToApiEndpoints, ZodApiEndpoints } from "../../../src";

const JsonHeader = z.object({
"Content-Type": z.literal("application/json"),
Expand Down
Loading