{
  "slug": "request-validator",
  "runtimes": {
    "node": {
      "frameworks": {
        "express": {
          "dependencies": {
            "runtime": [
              "zod",
              "dotenv-flow",
              "cross-env"
            ],
            "dev": []
          },
          "env": [],
          "architectures": {
            "mvc": {
              "files": [
                {
                  "type": "file",
                  "path": "src/app.test.ts",
                  "content": "import express, { type Application } from \"express\";\nimport \"dotenv-flow/config\";\nimport UserRouter from \"./routes/user.routes\";\n\nconst app: Application = express();\n\napp.use(express.json());\n\n// routes here\napp.use(\"/api/v1/users\", UserRouter);\n\nexport default app;\n"
                },
                {
                  "type": "file",
                  "path": "src/validations/user.validation.ts",
                  "content": "import { z } from \"zod\";\n\nexport const createUserSchema = z.object({\n  name: z.string().min(2, \"Name must be at least 2 characters\"),\n  email: z.email(\"Invalid email address\"),\n  password: z.string().min(8, \"Password must be at least 8 characters\"),\n  age: z.number().int().min(18).max(100).optional()\n});\n"
                },
                {
                  "type": "file",
                  "path": "src/constants/status-codes.ts",
                  "content": "export const STATUS_CODES = {\n  // 2xx Success\n  OK: 200,\n  CREATED: 201,\n  ACCEPTED: 202,\n  NO_CONTENT: 204,\n\n  // 3xx Redirection\n  MOVED_PERMANENTLY: 301,\n  FOUND: 302,\n  NOT_MODIFIED: 304,\n\n  // 4xx Client Errors\n  BAD_REQUEST: 400,\n  UNAUTHORIZED: 401,\n  FORBIDDEN: 403,\n  NOT_FOUND: 404,\n  CONFLICT: 409,\n  UNPROCESSABLE_ENTITY: 422,\n  TOO_MANY_REQUESTS: 429,\n\n  // 5xx Server Errors\n  INTERNAL_SERVER_ERROR: 500,\n  NOT_IMPLEMENTED: 501,\n  BAD_GATEWAY: 502,\n  SERVICE_UNAVAILABLE: 503,\n  GATEWAY_TIMEOUT: 504\n} as const;\n\nexport type StatusCode = (typeof STATUS_CODES)[keyof typeof STATUS_CODES];\n"
                },
                {
                  "type": "file",
                  "path": "src/utils/api-error.ts",
                  "content": "import { STATUS_CODES, StatusCode } from \"../constants/status-codes\";\n\nexport class ApiError extends Error {\n  public readonly statusCode: StatusCode;\n  public readonly isOperational: boolean;\n  public readonly errors?: unknown;\n\n  constructor(\n    statusCode: StatusCode,\n    message: string,\n    errors?: unknown,\n    isOperational = true\n  ) {\n    super(message);\n    this.name = \"ApiError\";\n    this.statusCode = statusCode;\n    this.errors = errors;\n    this.isOperational = isOperational;\n\n    Error.captureStackTrace(this, this.constructor);\n  }\n\n  static badRequest(message = \"Bad Request\", errors?: unknown) {\n    return new ApiError(STATUS_CODES.BAD_REQUEST, message, errors);\n  }\n\n  static unauthorized(message = \"Unauthorized\") {\n    return new ApiError(STATUS_CODES.UNAUTHORIZED, message);\n  }\n\n  static forbidden(message = \"Forbidden\") {\n    return new ApiError(STATUS_CODES.FORBIDDEN, message);\n  }\n\n  static notFound(message = \"Not Found\") {\n    return new ApiError(STATUS_CODES.NOT_FOUND, message);\n  }\n\n  static conflict(message = \"Conflict\") {\n    return new ApiError(STATUS_CODES.CONFLICT, message);\n  }\n\n  static server(message = \"Internal Server Error\") {\n    return new ApiError(STATUS_CODES.INTERNAL_SERVER_ERROR, message);\n  }\n}\n\n/*\n  ? Usage:\n  * throw new ApiError(404, \"Not found\");\n  * throw ApiError.badRequest(\"Bad request\");\n */\n"
                },
                {
                  "type": "file",
                  "path": "src/middlewares/validate-request.ts",
                  "content": "import { Request, Response, NextFunction } from \"express\";\nimport z, { ZodError, type ZodObject } from \"zod\";\n\nimport { ApiError } from \"../utils/api-error\";\n\nexport const validateRequest = (schema: ZodObject<any>) => {\n  return (req: Request, res: Response, next: NextFunction) => {\n    try {\n      schema.parse(req.body);\n\n      next();\n    } catch (error) {\n      if (!(error instanceof ZodError)) {\n        return next(error);\n      }\n\n      throw ApiError.badRequest(\n        \"Invalid request data\",\n        z.flattenError(error).fieldErrors || z.flattenError(error)\n      );\n    }\n  };\n};\n"
                },
                {
                  "type": "file",
                  "path": "src/routes/user.routes.ts",
                  "content": "import { Router } from \"express\";\nimport { validateRequest } from \"../middlewares/validate-request\";\nimport { createUserSchema } from \"../validations/user.validation\";\nimport { STATUS_CODES } from \"../constants/status-codes\";\n\nconst router = Router();\n\nrouter.post(\"/\", validateRequest(createUserSchema), (req, res) => {\n  return res.status(STATUS_CODES.CREATED).json({\n    success: true,\n    message: \"User created successfully\",\n    data: req.body\n  });\n});\n\nexport default router;\n"
                }
              ]
            },
            "feature": {
              "files": [
                {
                  "type": "file",
                  "path": "src/app.test.ts",
                  "content": "import express, { type Application } from \"express\";\nimport Routes from \"./routes/index\";\n\nconst app: Application = express();\n\napp.use(express.json());\n\n// routes here\napp.use(\"/api\", Routes);\n\nexport default app;\n"
                },
                {
                  "type": "file",
                  "path": "src/routes/index.ts",
                  "content": "import { Router } from \"express\";\nimport UserRouter from \"../modules/user/user.routes\";\n\nconst router = Router();\n\nrouter.use(\"/v1/users\", UserRouter);\n\nexport default router;\n"
                },
                {
                  "type": "file",
                  "path": "src/shared/middlewares/validate-request.ts",
                  "content": "import { Request, Response, NextFunction } from \"express\";\nimport z, { ZodError, type ZodObject } from \"zod\";\n\nimport { ApiError } from \"../errors/api-error\";\n\nexport const validateRequest = (schema: ZodObject<any>) => {\n  return (req: Request, res: Response, next: NextFunction) => {\n    try {\n      schema.parse(req.body);\n\n      next();\n    } catch (error) {\n      if (!(error instanceof ZodError)) {\n        return next(error);\n      }\n\n      throw ApiError.badRequest(\n        \"Invalid request data\",\n        z.flattenError(error).fieldErrors || z.flattenError(error)\n      );\n    }\n  };\n};\n"
                },
                {
                  "type": "file",
                  "path": "src/shared/errors/api-error.ts",
                  "content": "import { STATUS_CODES, StatusCode } from \"../constants/status-codes\";\n\nexport class ApiError extends Error {\n  public readonly statusCode: StatusCode;\n  public readonly isOperational: boolean;\n  public readonly errors?: unknown;\n\n  constructor(\n    statusCode: StatusCode,\n    message: string,\n    errors?: unknown,\n    isOperational = true\n  ) {\n    super(message);\n    this.name = \"ApiError\";\n    this.statusCode = statusCode;\n    this.errors = errors;\n    this.isOperational = isOperational;\n\n    Error.captureStackTrace(this, this.constructor);\n  }\n\n  static badRequest(message = \"Bad Request\", errors?: unknown) {\n    return new ApiError(STATUS_CODES.BAD_REQUEST, message, errors);\n  }\n\n  static unauthorized(message = \"Unauthorized\") {\n    return new ApiError(STATUS_CODES.UNAUTHORIZED, message);\n  }\n\n  static forbidden(message = \"Forbidden\") {\n    return new ApiError(STATUS_CODES.FORBIDDEN, message);\n  }\n\n  static notFound(message = \"Not Found\") {\n    return new ApiError(STATUS_CODES.NOT_FOUND, message);\n  }\n\n  static conflict(message = \"Conflict\") {\n    return new ApiError(STATUS_CODES.CONFLICT, message);\n  }\n\n  static server(message = \"Internal Server Error\") {\n    return new ApiError(STATUS_CODES.INTERNAL_SERVER_ERROR, message);\n  }\n}\n\n/*\n  ? Usage:\n  * throw new ApiError(404, \"Not found\");\n  * throw ApiError.badRequest(\"Bad request\");\n */\n"
                },
                {
                  "type": "file",
                  "path": "src/shared/constants/status-codes.ts",
                  "content": "export const STATUS_CODES = {\n  // 2xx Success\n  OK: 200,\n  CREATED: 201,\n  ACCEPTED: 202,\n  NO_CONTENT: 204,\n\n  // 3xx Redirection\n  MOVED_PERMANENTLY: 301,\n  FOUND: 302,\n  NOT_MODIFIED: 304,\n\n  // 4xx Client Errors\n  BAD_REQUEST: 400,\n  UNAUTHORIZED: 401,\n  FORBIDDEN: 403,\n  NOT_FOUND: 404,\n  CONFLICT: 409,\n  UNPROCESSABLE_ENTITY: 422,\n  TOO_MANY_REQUESTS: 429,\n\n  // 5xx Server Errors\n  INTERNAL_SERVER_ERROR: 500,\n  NOT_IMPLEMENTED: 501,\n  BAD_GATEWAY: 502,\n  SERVICE_UNAVAILABLE: 503,\n  GATEWAY_TIMEOUT: 504\n} as const;\n\nexport type StatusCode = (typeof STATUS_CODES)[keyof typeof STATUS_CODES];\n"
                },
                {
                  "type": "file",
                  "path": "src/modules/user/user.validation.ts",
                  "content": "import { z } from \"zod\";\n\nexport const createUserSchema = z.object({\n  name: z.string().min(2, \"Name must be at least 2 characters\"),\n  email: z.email(\"Invalid email address\"),\n  password: z.string().min(8, \"Password must be at least 8 characters\"),\n  age: z.number().int().min(18).max(100).optional()\n});\n"
                },
                {
                  "type": "file",
                  "path": "src/modules/user/user.routes.ts",
                  "content": "import { Router } from \"express\";\nimport { validateRequest } from \"../../shared/middlewares/validate-request\";\nimport { createUserSchema } from \"./user.validation\";\nimport { STATUS_CODES } from \"../../shared/constants/status-codes\";\n\nconst router = Router();\n\nrouter.post(\"/\", validateRequest(createUserSchema), (req, res) => {\n  return res.status(STATUS_CODES.CREATED).json({\n    success: true,\n    message: \"User created successfully\",\n    data: req.body\n  });\n});\n\nexport default router;\n"
                }
              ]
            }
          }
        },
        "nextjs": {
          "dependencies": {
            "runtime": [
              "zod"
            ],
            "dev": []
          },
          "env": [],
          "architectures": {
            "file-api": {
              "files": [
                {
                  "type": "file",
                  "path": "src/lib/validate-request.ts",
                  "content": "import { z, ZodType } from \"zod\";\r\n\r\ntype ValidationResult<T> =\r\n  | {\r\n      success: true;\r\n      data: T;\r\n    }\r\n  | {\r\n      success: false;\r\n      errors: Record<string, string[]>;\r\n    };\r\n\r\nexport function validateRequest<TSchema extends ZodType>(\r\n  schema: TSchema,\r\n  input: unknown\r\n): ValidationResult<z.infer<TSchema>> {\r\n  const result = schema.safeParse(input);\r\n\r\n  if (result.success) {\r\n    return {\r\n      success: true,\r\n      data: result.data\r\n    };\r\n  }\r\n\r\n  const formattedErrors: Record<string, string[]> = {};\r\n\r\n  result.error.issues.forEach(err => {\r\n    const key = err.path.join(\".\") || \"root\";\r\n\r\n    if (!formattedErrors[key]) {\r\n      formattedErrors[key] = [];\r\n    }\r\n\r\n    formattedErrors[key].push(err.message);\r\n  });\r\n\r\n  return {\r\n    success: false,\r\n    errors: formattedErrors\r\n  };\r\n}\r\n\r\n/*\r\n? Usage:\r\n\r\nimport { validateRequest } from \"@/lib/validate-request\";\r\n\r\nconst body = await req.json();\r\nconst result = validateRequest(SigninSchema, body);\r\n\r\nif (!result.success) {\r\n  return ApiResponse({\r\n    success: false,\r\n    statusCode: STATUS_CODES.BAD_REQUEST,\r\n    message: \"Invalid data received!\",\r\n    error: result.errors\r\n  });\r\n}\r\n\r\nconst { email, password } = result.data;\r\n*/"
                }
              ]
            }
          }
        }
      }
    }
  }
}
