Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
9112e32
first working draft with zod schema
scheidtdav May 7, 2026
cb00d78
Merge branch 'dev' into feat/openapi-docs
scheidtdav May 7, 2026
6dc641c
draft full workflow
scheidtdav May 7, 2026
53a3411
fix: rm unused apis
jona159 May 15, 2026
3f2ba10
feat: update readme section about openapi docs
jona159 May 15, 2026
50d3109
feat: add auth token to security schemes
jona159 May 15, 2026
8d217d2
feat(wip): update openapi docs
jona159 May 15, 2026
6d93269
Merge branch 'dev' into feat/openapi-docs
jona159 May 20, 2026
a1d39ac
feat: add abstraction layer for openapi errors and responses
jona159 May 20, 2026
6bb4cc8
feat: centralize openapi schemas, messages etc.
jona159 May 21, 2026
be3731d
fix: api route paths
jona159 May 21, 2026
4afeb45
feat: document stats endpoint
jona159 May 21, 2026
1e55582
fix: measurements instead of sensors as second stats value, as in old…
jona159 May 21, 2026
90d69f4
feat: add openapi schemas, factories, responses
jona159 May 21, 2026
9d65d25
feat: document api routes
jona159 May 21, 2026
6df030e
fix: deprecated
jona159 May 26, 2026
3b7535e
feat: add generic message response
jona159 May 26, 2026
78064b9
fix: simplify schemas, bugix for deleteAllMeasurements param
jona159 May 26, 2026
8ac6537
fix: date params as iso strings, add test case for deleteAllMeasurements
jona159 May 26, 2026
d9304db
feat: reuse iso date time schema
jona159 May 26, 2026
e27f614
fix: rm unused schemas, unify response type
jona159 May 26, 2026
c018371
fix(tests): call json method on response, rm duplicate test
jona159 May 26, 2026
bd4409f
fix: format
jona159 May 26, 2026
548c866
fix: add helper, fix content-type
jona159 May 26, 2026
1a1b96b
fix: create test user with valid tld email
jona159 May 26, 2026
eba1455
fix: duplicate function call
jona159 May 26, 2026
7ee9c2c
fix: transform user to api format
jona159 May 26, 2026
4da5481
fix: wording
jona159 May 27, 2026
956fb1d
feat: migrate to zod-openapi
jona159 May 27, 2026
9124ea9
fix: wording, add schemas, fix deprecation errors
jona159 May 27, 2026
f172108
feat: rework integration docs
jona159 May 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,6 @@ jobs:
- name: 📥 Install deps
run: npm install

- name: 🧾 Generate OpenAPI spec
run: npm run build:docs

- name: 🔎 Type check
run: npm run typecheck --if-present

Expand Down
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ node_modules

/build
/public/build
/public/openapi.json
.env
.cache

Expand All @@ -26,4 +25,3 @@ measurements.csv

/minio-data
/rustfs-data
/public/openapi.json
208 changes: 134 additions & 74 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,91 +163,151 @@ flexibility to adjust the outputs to the needs of the respective use case.

##### Documenting an API Route

The [swaggerJsdoc Library](https://www.npmjs.com/package/swagger-jsdoc) reads
the JSDoc-annotated source code in the api-routes and generates an
openAPI(Swagger) specification and is rendered using
[Swaggger UI](https://swagger.io/tools/swagger-ui/). The
[JSDoc annotations](https://github.com/Surnet/swagger-jsdoc) is usually added
before the loader or action function in the API Routes. The documentation will
then be automatically generated from the JSDoc annotations in all the api
routes. When testing the api during development do not forget to change the
server to [Development Server](http://localhost:3000). To authorize a user you
must provide the token obtained after sign-in. You can just copy and paste the
token in the value field and then hit the authorize button.

##### JSDoc Example

Here's an example of how to document an API route using JSDoc annotations:

```javascript
/**
* @openapi
* /api/users/{id}:
* get:
* summary: Get user by ID
* description: Retrieve a single user by their unique identifier
* tags:
* - Users
* parameters:
* - in: path
* name: id
* required: true
* description: Unique identifier of the user
* schema:
* type: string
* example: "12345"
* responses:
* 200:
* description: User retrieved successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* id:
* type: string
* example: "12345"
* name:
* type: string
* example: "John Doe"
* email:
* type: string
* example: "john.doe@example.com"
* createdAt:
* type: string
* format: date-time
* example: "2023-01-15T10:30:00Z"
* 404:
* description: User not found
* content:
* application/json:
* schema:
* type: object
* properties:
* error:
* type: string
* example: "User not found"
* 500:
* description: Internal server error
*/
export async function loader({ params }) {
API route documentation is generated from route-local `zod-openapi`
definitions. Each API route can export an `openapi` object that describes the
route's OpenAPI path item. Request bodies, response bodies, path parameters,
query parameters, and headers should be described with Zod schemas wherever
possible.

The main benefit of this approach is that schemas can be shared between
validation and documentation. This keeps the OpenAPI documentation closer to the
actual implementation and reduces the risk of outdated docs.

The generated OpenAPI specification is rendered using
[Swagger UI](https://swagger.io/tools/swagger-ui/). When testing the API during
development, do not forget to change the server to the
[Development Server](http://localhost:3000). To authorize a user, provide the
token obtained after sign-in. You can copy and paste the token into the value
field and then hit the authorize button.

##### zod-openapi Example

Here's an example of how to document an API route using `zod-openapi`:

```typescript
import { z } from 'zod'
import { type ZodOpenApiPathItemObject } from 'zod-openapi'
import { type Route } from './+types/api.users.$id'

const UserPathParamsSchema = z.object({
id: z.string().min(1).meta({
description: 'Unique identifier of the user',
example: '12345',
}),
})

const UserSchema = z
.object({
id: z.string().meta({
description: 'Unique identifier of the user',
example: '12345',
}),
name: z.string().meta({
description: "User's display name",
example: 'John Doe',
}),
email: z.string().email().meta({
description: "User's email address",
example: 'john.doe@example.com',
}),
createdAt: z.string().datetime().meta({
description: 'Account creation timestamp',
example: '2023-01-15T10:30:00.000Z',
}),
})
.meta({
id: 'User',
description: 'User information object',
})

const NotFoundErrorSchema = z
.object({
code: z.literal('Not Found'),
message: z.literal('User not found'),
error: z.literal('User not found'),
})
.meta({ id: 'NotFoundError' })

const InternalServerErrorSchema = z
.object({
code: z.literal('Internal Server Error'),
message: z.literal('Internal server error'),
error: z.literal('Internal server error'),
})
.meta({ id: 'InternalServerError' })

export const openapi: ZodOpenApiPathItemObject = {
get: {
tags: ['Users'],
summary: 'Get user by ID',
description: 'Retrieve a single user by their unique identifier',
operationId: 'getUserById',

requestParams: {
path: UserPathParamsSchema,
},

responses: {
200: {
description: 'User retrieved successfully',
content: {
'application/json': {
schema: UserSchema,
},
},
},
404: {
description: 'User not found',
content: {
'application/json': {
schema: NotFoundErrorSchema,
},
},
},
500: {
description: 'Internal server error',
content: {
'application/json': {
schema: InternalServerErrorSchema,
},
},
},
},
},
}

export async function loader({ params }: Route.LoaderArgs) {
const { id } = params

try {
const user = await getUserById(id)

if (!user) {
throw new Response('User not found', { status: 404 })
return StandardResponse.notFound('User not found')
}

const parsed = await UserSchema.safeParseAsync(user)

if (!parsed.success) {
return StandardResponse.internalServerError()
}
return Response.json({ user })

return StandardResponse.ok(parsed.data)
} catch (error) {
throw new Response('Internal server error', { status: 500 })
console.warn(error)
return StandardResponse.internalServerError('Internal server error')
}
}
```

This JSDoc annotation will automatically generate comprehensive API
documentation including endpoint details, parameters, response schemas, and
example values.
The exported `openapi` object is picked up automatically when generating the
OpenAPI specification. Prefer defining reusable Zod schemas for request bodies,
response bodies, path parameters, query parameters, and shared error responses.

Use `.meta(...)` to add OpenAPI-specific metadata such as descriptions,
examples, component ids, and formatting hints. For route parameters, prefer
`requestParams` over manually writing OpenAPI `parameters`, because it allows
the parameters to be described directly with Zod schemas.

#### Testing

Expand Down
1 change: 1 addition & 0 deletions app/lib/env.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export function init() {
export function getEnv() {
return {
NOMINATIM_SEARCH_API: process.env.NOMINATIM_SEARCH_API,
OSEM_GITHUB_URL: process.env.OSEM_API_URL,
MODE: process.env.NODE_ENV,
DIRECTUS_URL: process.env.DIRECTUS_URL,
MYBADGES_API_URL: process.env.MYBADGES_API_URL,
Expand Down
Loading