Skip to content

Use zod-openapi for API documentation#933

Open
scheidtdav wants to merge 31 commits into
devfrom
feat/openapi-docs
Open

Use zod-openapi for API documentation#933
scheidtdav wants to merge 31 commits into
devfrom
feat/openapi-docs

Conversation

@scheidtdav
Copy link
Copy Markdown
Member

@scheidtdav scheidtdav commented May 7, 2026

Type of Change

  • Dependency upgrade
  • Bug fix (non-breaking change)
  • Breaking change
    • e.g. a fixed bug or new feature that may break something else
  • New feature
  • Code quality improvements
    • e.g. refactoring, documentation, tests, tooling, ...

Implementation

As a POC I have replaced the documentation for the sign-in route with one generated via zod-openapi and corresponding zod schemas (that could potentially be shared between routes).
The docs page also got a little bit of an overhaul to make it easier to differentiate between regular API docs and integrations docs.

The zod schema makes the code of the actual api handler a lot more simple (there is almost no logic anymore). All of the "complexity" is within the schema definition which does generate its entire documentation as a side product. This nicely keeps the documentation aligned with the actual implementation and not only with whats supposedly implemented (a problem we have with the old docs and one that I came across while implementing this too!).

The build step is now also gone. What looks like a runtime generation is a little smarter though: Vites import.meta.glob is actually derived via statical analysis and thus should be very fast.

Totally open for opinions. Should I continue this?

Sidenote: Since there is nothing generated about the integrations spec, we might want to place it somewhere else or even convert it to an actual .yml file once and just put it under public assets?
There must be a better way than this right now... 🤔

Checklist

  • I gave this pull request a meaningful title
  • My pull request is targeting the dev branch
  • [] I have added documentation to my code <-- README needs an update
  • I have deleted code that I have commented out

Additional Information

@github-actions
Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 70.55% 1962 / 2781
🔵 Statements 69.02% 2032 / 2944
🔵 Functions 69.46% 380 / 547
🔵 Branches 56.41% 980 / 1737
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
app/lib/user-transform.ts 100% 50% 100% 100%
app/lib/openapi/messages.ts 100% 100% 100% 100%
app/lib/openapi/errors/factories.ts 100% 100% 100% 100%
app/lib/openapi/errors/index.ts 100% 100% 100% 100%
app/lib/openapi/errors/responses.ts 96.29% 90.9% 92.3% 96.15% 48
app/lib/openapi/errors/schemas.ts 100% 100% 100% 100%
app/lib/openapi/schemas/claim.ts 100% 100% 100% 100%
app/lib/openapi/schemas/common.ts 100% 100% 100% 100%
app/lib/openapi/schemas/device.ts 100% 100% 100% 100%
app/lib/openapi/schemas/measurement.ts 100% 100% 100% 100%
app/lib/openapi/schemas/sensor.ts 100% 100% 100% 100%
app/lib/openapi/schemas/user.ts 100% 100% 100% 100%
app/middleware/content-type-header.server.ts 25% 0% 0% 25% 11-15, 26-28
app/routes/api.boxes.$deviceId.$sensorId.measurements.ts 78% 69.04% 100% 77.77% 111-113, 168-170, 175-177, 180, 190-192, 220-226
app/routes/api.boxes.$deviceId.data.$sensorId.ts 72.6% 50.9% 100% 80.95% 208, 225, 228, 251-252, 261-263, 271-277, 299, 302, 307, 313-322, 331, 334, 337, 341, 344
app/routes/api.boxes.$deviceId.locations.ts 80.48% 62.5% 100% 89.18% 183, 190, 228-229, 247, 258, 262, 266
app/routes/api.boxes.$deviceId.script.ts 77.41% 40% 80% 77.41% 247-253, 276-285, 293-307
app/routes/api.boxes.$deviceId.sensors.ts 79.16% 72.72% 100% 79.16% 111, 123, 130, 135-136
app/routes/api.boxes.$deviceId.ts 67.53% 47.69% 57.14% 68.91% 104-109, 292, 301-315, 323, 329-336, 345, 354-357, 362-368, 374-400, 424, 464-471, 478, 483-485, 495-496
app/routes/api.boxes.claim.ts 80% 47.82% 100% 84.84% 158, 175, 198-208
app/routes/api.boxes.transfer.$deviceId.ts 85.71% 71.87% 100% 94% 191-193, 197, 223-225, 229, 231, 259, 261, 310
app/routes/api.boxes.transfer.ts 80% 57.5% 100% 82.45% 210-212, 215, 268-270, 277, 279, 285-286, 303-313
app/routes/api.boxes.ts 91.3% 100% 100% 91.3% 202-203, 260-261
app/routes/api.stats.ts 88.23% 100% 100% 88.23% 120-121
app/routes/api.users.me.boxes.$deviceId.ts 75% 62.5% 100% 75% 89-91, 96, 100-102, 109-110
app/routes/api.users.me.resend-email-confirmation.ts 73.33% 50% 100% 73.33% 85-87, 92-94, 104-105
app/routes/api.users.me.ts 78.65% 77.55% 100% 80.23% 89-93, 291, 294, 320-321, 326-327, 346, 357, 372, 395-396, 410-411, 416-417, 444, 459-460
app/routes/api.users.password-reset.ts 76.19% 71.42% 100% 76.19% 138, 161-173
app/routes/api.users.refresh-auth.ts 83.33% 76.47% 100% 83.33% 166-173
app/routes/api.users.register.ts 71.05% 47.36% 100% 71.05% 211-213, 219, 224, 230, 267-280
app/routes/api.users.request-password-reset.ts 77.77% 50% 100% 77.77% 100, 106, 119-120
app/routes/api.users.sign-in.ts 83.33% 83.33% 100% 83.33% 136, 151-152
app/services/statistics-service.server.ts 100% 100% 100% 100%
tests/data/generate_test_user.ts 100% 100% 100% 100%
Generated in workflow #2583 for commit 4da5481 by the Vitest Coverage Report Action

@jona159 jona159 marked this pull request as ready for review May 28, 2026 05:21
z.string().meta({ example: 'Bad request.' }),
).meta({
id: 'BadRequestError',
})
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm just starting to dig into your code (so take this first comment with a grain of salt): Don't we have the factory methods in factories.ts for creating error schemas? Plus: The implementation here seems to contain more information than the factory version. 🤔

Is there a reason we have both?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

How come we don't use the location schema in the device schema? It seems to me that we are defining what a location is in multiple locations.

{
"openapi": "3.1.0",
"info": {
"title": "My API",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is My API overwritten somewhere? If not, we should give this a meaningful name 😉

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

So far, only the documentation part of the routes has been replaced with the zod schema implementaiton but the logic itself (e.g. in this file line 285 onward) is still implemented by hand and doing the validation and error handling itself. According to my understanding, this should actually be covered with the zod schemas right? 🤔

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I did a bit of research: Maybe we could already benefit from the schemas if we use them for the validation of incoming requests during runtime? If we put that into a middleware, we would not need to adjust all the route implementations.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Another thought: The ticket is about using another (here: zod-openapi) documentation framework. You could argue that we are thus good with "only" using it for dumentation purposes in this PR. Some kind of Schema validation would still be nice, though

badRequestResponse,
internalServerErrorResponse,
notFoundResponse,
} from '~/lib/openapi/errors'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Two import statements for the same file? Also in the other routes


import * as z from 'zod/v4'
import 'zod-openapi'
import { type ZodOpenApiPathItemObject } from 'zod-openapi'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Duplicate import

internalServerErrorResponse,
methodNotAllowedResponse,
notFoundResponse,
} from '~/lib/openapi/errors'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

duplicate import

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this route only for the deletion of measurements?

},
}

export async function action({ request, params }: Route.ActionArgs) {
Copy link
Copy Markdown
Member

@zven zven May 29, 2026

Choose a reason for hiding this comment

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

Method seems to duplicate logic and error handling that is already in the schemas?

Comment thread app/routes/api.stats.ts
import {
badRequestResponse,
internalServerErrorResponse,
} from '~/lib/openapi/errors'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

double import

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.

swagger-jsdoc should be replaced

3 participants