Open PRE-ShashankPatil opened 2 months ago
Missing code:
const app: Express = express();
app.use(express.urlencoded())
app.use(express.json()) // need to add for request body parser
Also add example for post method zod with open api
Same here. Could not create post requests. Didn't manage to make Swagger working but via Postman the create endpoint works. Here is a simple code for creating a user:
userModel.ts
import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi';
import { z } from 'zod';
import { commonValidations } from '@/common/utils/commonValidation';
extendZodWithOpenApi(z);
export type User = z.infer<typeof UserSchema>;
export const UserSchema = z.object({
id: z.number(),
name: z.string(),
createdAt: z.date(),
});
// Input Validation for 'GET users/:id' endpoint
export const GetUserSchema = z.object({
params: z.object({ id: commonValidations.id }),
});
export const CreateUserSchema = z.object({
body: z.object(
{
name: z.string().min(1, 'Name is required'),
}
)
});
userRepository.ts
import { User } from '@/api/user/userModel';
export const users: User[] = [
{ id: 1, name: 'Alice', createdAt: new Date() },
{ id: 2, name: 'Bob', createdAt: new Date() },
];
export const userRepository = {
findAllAsync: async (): Promise<User[]> => {
return users;
},
findByIdAsync: async (id: number): Promise<User | null> => {
return users.find((user) => user.id === id) || null;
},
createAsync: async (userData: User): Promise<User> => {
const newUser = {
...userData,
id: users.length + 1, // Simple way to generate a new ID
createdAt: new Date(),
};
users.push(newUser);
return newUser;
}
};
userService
import { StatusCodes } from 'http-status-codes';
import { User } from '@/api/user/userModel';
import { userRepository } from '@/api/user/userRepository';
import { ResponseStatus, ServiceResponse } from '@/common/models/serviceResponse';
import { logger } from '@/server';
export const userService = {
// Retrieves all users from the database
findAll: async (): Promise<ServiceResponse<User[] | null>> => {
try {
const users = await userRepository.findAllAsync();
if (!users) {
return new ServiceResponse(ResponseStatus.Failed, 'No Users found', null, StatusCodes.NOT_FOUND);
}
return new ServiceResponse<User[]>(ResponseStatus.Success, 'Users found', users, StatusCodes.OK);
} catch (ex) {
const errorMessage = `Error finding all users: $${(ex as Error).message}`;
logger.error(errorMessage);
return new ServiceResponse(ResponseStatus.Failed, errorMessage, null, StatusCodes.INTERNAL_SERVER_ERROR);
}
},
// Retrieves a single user by their ID
findById: async (id: number): Promise<ServiceResponse<User | null>> => {
try {
const user = await userRepository.findByIdAsync(id);
if (!user) {
return new ServiceResponse(ResponseStatus.Failed, 'User not found', null, StatusCodes.NOT_FOUND);
}
return new ServiceResponse<User>(ResponseStatus.Success, 'User found', user, StatusCodes.OK);
} catch (ex) {
const errorMessage = `Error finding user with id ${id}:, ${(ex as Error).message}`;
logger.error(errorMessage);
return new ServiceResponse(ResponseStatus.Failed, errorMessage, null, StatusCodes.INTERNAL_SERVER_ERROR);
}
},
// Creates a new user
createUser: async (userData: User): Promise<ServiceResponse<User | null>> => {
try {
const newUser = await userRepository.createAsync(userData);
return new ServiceResponse<User>(
ResponseStatus.Success,
'User created successfully',
newUser,
StatusCodes.CREATED
);
} catch (ex) {
const errorMessage = `Error creating user: ${(ex as Error).message}`;
const newUser = await userRepository.createAsync(userData);
logger.error(errorMessage);
return new ServiceResponse<User>(
ResponseStatus.Failed,
errorMessage,
newUser,
StatusCodes.INTERNAL_SERVER_ERROR
);
}
},
};
userRouter
import { OpenAPIRegistry } from '@asteasolutions/zod-to-openapi';
import express, { Request, Response, Router } from 'express';
import { z } from 'zod';
import { GetUserSchema, UserSchema, CreateUserSchema } from '@/api/user/userModel';
import { userService } from '@/api/user/userService';
import { createApiResponse } from '@/api-docs/openAPIResponseBuilders';
import { handleServiceResponse, validateRequest } from '@/common/utils/httpHandlers';
export const userRegistry = new OpenAPIRegistry();
userRegistry.register('User', UserSchema);
export const userRouter: Router = (() => {
const router = express.Router();
userRegistry.registerPath({
method: 'get',
path: '/users',
tags: ['User'],
responses: createApiResponse(z.array(UserSchema), 'Success'),
});
router.get('/', async (_req: Request, res: Response) => {
const serviceResponse = await userService.findAll();
handleServiceResponse(serviceResponse, res);
});
userRegistry.registerPath({
method: 'get',
path: '/users/{id}',
tags: ['User'],
request: { params: GetUserSchema.shape.params },
responses: createApiResponse(UserSchema, 'Success'),
});
router.get('/:id', validateRequest(GetUserSchema), async (req: Request, res: Response) => {
const id = parseInt(req.params.id as string, 10);
const serviceResponse = await userService.findById(id);
handleServiceResponse(serviceResponse, res);
});
userRegistry.registerPath({
method: 'post',
path: '/users',
tags: ['User'],
request: { body: {
content: {
"application/json": {
schema: CreateUserSchema,
},
}
} },
responses: createApiResponse(UserSchema, 'Success'),
});
// POST /users - Create a new user
router.post('/', validateRequest(CreateUserSchema), async (req: Request, res: Response) => {
const newUserData = req.body;
const serviceResponse = await userService.createUser(newUserData);
res.status(serviceResponse.statusCode).send(serviceResponse);
});
return router;
})();
server.ts
import cors from 'cors';
import express, { Express } from 'express';
import helmet from 'helmet';
import mongoose from 'mongoose';
import { pino } from 'pino';
import { healthCheckRouter } from '@/api/healthCheck/healthCheckRouter';
import { userRouter } from '@/api/user/userRouter';
import { openAPIRouter } from '@/api-docs/openAPIRouter';
import errorHandler from '@/common/middleware/errorHandler';
import rateLimiter from '@/common/middleware/rateLimiter';
import requestLogger from '@/common/middleware/requestLogger';
import { userPointsRouter } from './api/points/userPointsRouter';
const logger = pino({ name: 'server start' });
const app: Express = express();
app.use((req, res, next) => {
console.log(`Incoming request: ${req.method} ${req.path}`);
console.log('Body:', req.body);
next();
});
// MAKE SURE YOU HAVE THIS IN THE FOLLOWING ORDER
// Parsing application/json
app.use(express.json());
app.use(express.urlencoded({ extended: false }))
// Set the application to trust the reverse proxy
app.set('trust proxy', true);
// CORS configuration
const corsOptions = {
origin: 'https://localhost:5173', // Specify YOUR frontend URL
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
credentials: true, // Allow cookies to be sent
optionsSuccessStatus: 204,
};
// Middlewares
app.use(cors(corsOptions));
app.use(helmet());
app.use(rateLimiter);
// Request logging
app.use(requestLogger);
// Routes
app.use('/health-check', healthCheckRouter);
app.use('/users', userRouter);
app.use('/points', userPointsRouter); // Use the user points router
// Swagger UI
app.use(openAPIRouter);
// Error handlers
app.use(errorHandler());
export { app, logger };
@devdomsos In userModel instead of
export const CreateUserSchema = z.object({
body: z.object(
{
name: z.string().min(1, 'Name is required'),
}
)
});
use,
export const CreateUserSchema = z.object({
name: z.string().min(1, 'Name is required')
});
I managed to get around this by adding the following lines of code
server.ts
app.use(express.json())
httpHandlers.ts > validateRequest
schema.parse({
...req.body,
query: req.query,
params: req.params,
});
and my schema format is
export const CreateUserSchema = z.object({
name: z.string().min(1, 'Name is required'),
....
});
so that the client doesn't have to send a json with nested "body" property
Not able to crate post method.
Please provide one example of post, put method with schema validation.