Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Not able to crate post method with schema validation. #202

Open
PRE-ShashankPatil opened this issue Jun 23, 2024 · 10 comments
Open

Not able to crate post method with schema validation. #202

PRE-ShashankPatil opened this issue Jun 23, 2024 · 10 comments

Comments

@PRE-ShashankPatil
Copy link

Not able to crate post method.

Please provide one example of post, put method with schema validation.

@PRE-ShashankPatil
Copy link
Author

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

@devdomsos
Copy link

devdomsos commented Jul 13, 2024

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 };

@amabirbd
Copy link
Contributor

amabirbd commented Aug 15, 2024

@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')
});

@ktyntang
Copy link

ktyntang commented Aug 28, 2024

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

@609harsh
Copy link

609harsh commented Oct 23, 2024

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'
    }

@609harsh
Copy link

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

@amabirbd
Copy link
Contributor

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"),
});```

@609harsh
Copy link

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?

@amabirbd
Copy link
Contributor

amabirbd commented Oct 23, 2024

@609harsh you should probably pass SignupSchema instead of signupValidation in

  validateRequest(signupValidation)

@609harsh
Copy link

@609harsh you should probably pass SignupSchema instead of signupValidation in

  validateRequest(signupValidation)

oh my bad. in actual codebase it is SignupSchema only. I was just trying some other things ended up pasting this one😅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants