Skip to content

Expresskit validator #72

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

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open

Expresskit validator #72

wants to merge 21 commits into from

Conversation

sjorobekov
Copy link

@sjorobekov sjorobekov commented Jun 23, 2025

ExpressKit Validator

Validation framework for ExpressKit that provides:

  1. Request Validation - Validate request body, params, query, and headers
  2. Response Serialization - Validate and filter response data
  3. Swagger Documentation - Auto-generate OpenAPI specs from schemas
  4. Zod v4 Integration - Use Zod schemas for validation

Features

  • Type-Safe: Full TypeScript inference from Zod schemas
  • Async Support: Handles sync, async, and conditional data
  • Serialized data typesafety: Serialized data should follow response data schema

TODO

  • Make automatic validation of request, so req.body will be already validated in a handler
  • Request validation
  • Response type check
  • Reponse Serialization
  • Swagger integration
  • AuthMiddleware to swagger
  • Cache swagger final json schema
  • Tests
  • Allow customization of request type (json, form-data,etc)
  • Allow customization of response type from controller
  • Cache schemas

@sjorobekov sjorobekov mentioned this pull request Jun 23, 2025
10 tasks
@sjorobekov sjorobekov marked this pull request as ready for review July 14, 2025 19:10
@sjorobekov sjorobekov requested a review from imsitnikov July 14, 2025 19:10
summary?: string; // Short summary for OpenAPI
description?: string; // Detailed description for OpenAPI
tags?: string[]; // Tags for grouping (e.g., for OpenAPI)
manualValidation?: boolean; // Default: false. If true, call req.validate() manually.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not about a contract, but about internal logic of withContract.

In my opinion, it would be better to separate the OpenAPI settings and the HoF settings:

withContract(RouteContract, settings?: {manualValidation?: boolean})

// If there's no schema defined for this status code, or it's a 204, or data is undefined, don't send content
const hasSchema =
config.response.content[statusCode as number]?.schema !== undefined;
if (!hasSchema || statusCode === 204 || data === undefined) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should leave control over the 204 code to consumers?

* @returns The OpenAPI schema object
*/
getOpenApiSchema(): OpenApiSchemaObject {
if (cachedSchema) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
});

if (ctx.config.openApiRegistry?.enabled && openapiRegistry) {
const openApiSchema = openapiRegistry.getOpenApiSchema();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calculating openApiSchema can take a long time and block the application from starting. Is it possible to wrap these lines in setImmediate(...)?

import jwt from 'jsonwebtoken';

// Add OpenAPI security scheme metadata to your auth handler
const jwtAuthHandler = bearerAuth('myJwtAuth')(function authenticate(req, res, next) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it work correctly with global declared appAuthHandler?


The `res` object in your handler is enhanced with the following methods:

- **`res.sendTyped(statusCode, data?)`**:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sendTyped is still not the best name.

I'm not sure if patching the original res.send() is acceptable here, but if it is, I suggest the following naming:

  • res.send() – for static type checking (now res.sendTyped()).
  • res.sendOriginal() – for original method (now res.send()).
  • res.sendValidated() – without any changes.

Or use res.contract.send() and res.contract.sendValidated().

@resure what do you think about this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original express method should not be patched. Such an override can have a lot of side effects and is probably not worth it.

res.contract.send()

I'm not 100% sure if this is better than sendTyped, but maybe it could be a logical way to move all new helpers to new namespaces (req.contract., res.contract.). The only worry is that it might look pretty long that way.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@imsitnikov @resure what you think about having semantic methods: req.ok(data), req.created(data), req.badRequest(data), etc, depending on what is defined in schema? They will have static validation.
And probably req.ok.validated(data) for dynamic validation?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like additional pollution of the req object, and one method will be easier to maintain than many.
Overall, I'm okay with res.contract.send() or res.sendTyped(), but I'm not the decision maker here.

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

Successfully merging this pull request may close these issues.

3 participants