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 · 4 comments
Open

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

PRE-ShashankPatil opened this issue Jun 23, 2024 · 4 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

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

4 participants