edwinhern / express-typescript-2024

Express + TypeScript + Boilerplate for Web / API App
https://express.hernandezserver.com/
481 stars 103 forks source link

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

Open PRE-ShashankPatil opened 2 months ago

PRE-ShashankPatil commented 2 months ago

Not able to crate post method.

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

PRE-ShashankPatil commented 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

devdomsos commented 1 month ago

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 commented 3 weeks ago

@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 commented 1 week ago

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