Not able to crate post method with schema validation.
Not able to crate post method.
Please provide one example of post, put method with schema validation.
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
hi i am still getting this issue validation fail.
export const SignupSchema = z.object({
name: z.string(),
email: z.string().email(),
phoneNumber: z
.string()
.regex(/^\d{10}$/, "Invalid phone number. Must be 10 digits."),
});
export const validateRequest =
(schema: ZodSchema) => (req: Request, res: Response, next: NextFunction) => {
try {
schema.parse({ body: req.body, query: req.query, params: req.params });
next();
} catch (err) {
const errorMessage = `Invalid input: ${(err as ZodError).errors
.map((e) => e.message)
.join(", ")}`;
const statusCode = StatusCodes.BAD_REQUEST;
const serviceResponse = ServiceResponse.failure(
errorMessage,
null,
statusCode
);
console.log(err);
return handleServiceResponse(serviceResponse, res);
}
};
SignupRegistry.registerPath({
method: "post",
path: "/signup",
tags: ["Signup"],
requestBody: {
content: {
"application/json": {
schema: {
properties: {
name: {
type: "string",
example: "xyze",
},
email: {
type: "string",
example: "[email protected]",
},
phoneNumber: {
type: "string",
example: "9000000001",
},
},
},
},
},
},
responses: createApiResponse(z.array(SignupSchema), "Success"),
});
signupRouter.post(
"/",
validateRequest(SignupSchema),
signupController.createUser
);
following is the error
{
code: 'invalid_type',
expected: 'string',
received: 'undefined',
path: [Array],
message: 'Required'
},
{
code: 'invalid_type',
expected: 'string',
received: 'undefined',
path: [Array],
message: 'Required'
},
{
code: 'invalid_type',
expected: 'string',
received: 'undefined',
path: [Array],
message: '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
i tried this but getting TypeError: req.body is not a function
Structure for Post method with body should be something like this:
userRegistry.registerPath({
method: "post",
path: "/users",
tags: ["User"],
request: {
body: {
description: "post request create a User",
content: {
"application/json": {
schema: CreateUserSchema,
},
},
},
},
responses: createApiResponse(CreateUserSchema, "Success"),
});```
Structure for Post method with body should be something like this:
userRegistry.registerPath({ method: "post", path: "/users", tags: ["User"], request: { body: { description: "post request create a User", content: { "application/json": { schema: CreateUserSchema, }, }, }, }, responses: createApiResponse(CreateUserSchema, "Success"), });```
Thanks for this one. But validationRequest is the problem. Any suggestions on that?
@609harsh you should probably pass SignupSchema instead of signupValidation in
validateRequest(signupValidation)
@609harsh you should probably pass
SignupSchemainstead ofsignupValidationinvalidateRequest(signupValidation)
oh my bad. in actual codebase it is SignupSchema only. I was just trying some other things ended up pasting this one😅
sample post schema i have working - https://github.com/Decoupled-Saas/api/blob/main/src/schemas/authSchema.ts#L29 you need to wrap your schema in an additional body object