Last active
January 7, 2025 09:04
-
-
Save beauwilliams/899019319d64ff44ed3a8c10514339ff to your computer and use it in GitHub Desktop.
One zod to rule them all
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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