Skip to content

Instantly share code, notes, and snippets.

@beauwilliams
Last active January 7, 2025 09:04
Show Gist options
  • Save beauwilliams/899019319d64ff44ed3a8c10514339ff to your computer and use it in GitHub Desktop.
Save beauwilliams/899019319d64ff44ed3a8c10514339ff to your computer and use it in GitHub Desktop.
One zod to rule them all
import { Router, Request, Response, NextFunction } from "express";
import { z } from "zod";
import { OpenAPIRegistry, extendZodWithOpenApi } from "@asteasolutions/zod-to-openapi";
import { extendZod } from "@zodyac/zod-mongoose";
import { authenticateJWTMiddleware } from "./middlewares/authenticateJWTMiddleware";
import { zodSchema } from "@zodyac/zod-mongoose";
import { model } from "mongoose";
// Extend Zod for OpenAPI and Mongoose support
extendZodWithOpenApi(z);
extendZod(z);
// Create a registry for OpenAPI
const registry = new OpenAPIRegistry();
// Define and register the User schema
const UserSchema = registry.register(
"User",
z.object({
id: z.number(),
username: z.string().optional(),
points: z
.object({
gold: z.number().default(0),
real: z.number().default(0),
})
.default({ gold: 0, real: 0 }),
})
);
// Example usage of the registry to define routes
registry.registerPath({
method: "get",
path: "/user",
summary: "Retrieve authenticated user's profile",
parameters: [
{
name: "Authorization",
in: "header",
required: true,
schema: { type: "string", description: "JWT token for authentication" },
},
],
responses: {
200: {
description: "User profile retrieved successfully",
content: { "application/json": { schema: { $ref: "#/components/schemas/TelegramUser" } } },
},
404: { description: "User not found" },
400: { description: "Validation error" },
500: { description: "Internal server error" },
},
});
// Convert Zod schema to Mongoose schema
const UserMongooseSchema = zodSchema(UserSchema);
// Create the Mongoose model
export const UserModel = model(
"User",
UserMongooseSchema
);
// Example user handler implementation
export const getUserHandler = async (
req: Request,
res: Response
): Promise<void> => {
try {
const userPayload = UserSchema.parse(req.user);
const storedUser = await UserModel.findOne({
id: userPayload.id,
});
if (!storedUser) {
res.status(404).json({ error: "User not found" });
return;
}
res.json({ user: storedUser });
} catch (err) {
console.error("Error fetching user profile:", err);
if (err instanceof z.ZodError) {
res.status(400).json({ error: err.errors });
} else if (err instanceof Error) {
res.status(500).json({ error: err.message });
} else {
res.status(500).json({ error: "Internal server error" });
}
}
};
// Route handlers
const routeHandlers: Record<string, (req: Request, res: Response) => Promise<void>> = {
"/user": getUserHandler,
};
// Function to dynamically generate routes
export const generateRoutesFromRegistry = (router: Router) => {
const registeredPaths = registry.getRegisteredPaths(); // Fetch all registered paths
const methodMapping: Record<string, (path: string, ...handlers: any[]) => void> = {
get: router.get.bind(router),
post: router.post.bind(router),
put: router.put.bind(router),
delete: router.delete.bind(router),
patch: router.patch.bind(router),
};
registeredPaths.forEach(({ method, path, parameters }) => {
const middlewares: Array<(req: Request, res: Response, next: NextFunction) => void> = [];
// Add JWT middleware if "Authorization" header is required
if (parameters?.some((p) => p.name === "Authorization" && p.in === "header")) {
middlewares.push(authenticateJWTMiddleware);
}
const handler = routeHandlers[path];
if (!handler) {
console.error(No handler defined for route: ${path});
return;
}
const routeMethod = methodMapping[method.toLowerCase()];
if (!routeMethod) {
console.error(Unsupported HTTP method: ${method});
return;
}
// Register the route with method, middlewares, and handler
routeMethod(path, ...middlewares, handler);
});
};
// Dynamically generate api routes using our util
const apiRouter = Router();
generateRoutesFromRegistry(router);
//Bind them to express
app.use("/api", apiRouter);
//🚀 Start the server
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
//Regenerate zod schema in your frontend using openapi as intermediary
"generate:types": "openapi-zod-client https://your-server-address/openapi.json --output src/types/api.ts"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment