Skip to content

Commit 2e26f37

Browse files
committed
docs: update documentation
1 parent f1b971f commit 2e26f37

File tree

6 files changed

+314
-20
lines changed

6 files changed

+314
-20
lines changed

packages/documentation/src/app/getting-started/quick-start/page.mdx

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@ Cross-file references are also supported, so don't worry about pre-processing th
1414

1515
You can check the version we develop and test against [here](https://github.com/mnahkies/openapi-code-generator/blob/main/.github/workflows/ci.yml#L17).
1616

17-
<Tabs items={[<h2 style={{paddingRight: 20}}>Fetch Client</h2>, <h2 style={{paddingRight: 20}}>Axios Client</h2>, <h2 style={{paddingRight: 20}}>Koa Server</h2>]}>
17+
<Tabs items={[
18+
<h2 style={{paddingRight: 20}}>Fetch Client</h2>,
19+
<h2 style={{paddingRight: 20}}>Axios Client</h2>,
20+
<h2 style={{paddingRight: 20}}>Koa Server</h2>,
21+
<h2 style={{paddingRight: 20}}>Express Server</h2>,
22+
]}>
1823
<Tabs.Tab>
1924
<Steps>
2025
{<h3>Install dependencies</h3>}
@@ -24,7 +29,8 @@ You can check the version we develop and test against [here](https://github.com/
2429
npm i @nahkies/typescript-fetch-runtime
2530
```
2631

27-
You could also install the CLI globally if you prefer, but it's best to version it per project.
32+
You could also run the cli through `npx`, or install it globally if you prefer, but it's recommended to pin its
33+
version as a `devDependency` per project.
2834

2935
{<h3>Run generation</h3>}
3036
This will generate the client to `./src/generated/clients/some-service`
@@ -40,7 +46,7 @@ You can check the version we develop and test against [here](https://github.com/
4046
Use your new type-safe client, and never worry about making a typo in a url or parameter name again.
4147
Let the typescript compiler take care of checking that for you.
4248

43-
See [Guide to typescript-fetch client template](../guides/client-templates/typescript-fetch) for more details.
49+
See [Guide to typescript-fetch client template](/guides/client-templates/typescript-fetch) for more details.
4450
</Steps>
4551
</Tabs.Tab>
4652

@@ -52,7 +58,9 @@ You can check the version we develop and test against [here](https://github.com/
5258
npm i --dev @nahkies/openapi-code-generator
5359
npm i axios @nahkies/typescript-axios-runtime
5460
```
55-
You could also install the CLI globally if you prefer, but it's best to version it per project.
61+
62+
You could also run the cli through `npx`, or install it globally if you prefer, but it's recommended to pin its
63+
version as a `devDependency` per project.
5664

5765
{<h3>Run generation</h3>}
5866
This will generate the client to `./src/generated/clients/some-service`
@@ -69,7 +77,7 @@ You can check the version we develop and test against [here](https://github.com/
6977
Use your new type-safe client, and never worry about making a typo in a url or parameter name again.
7078
Let the typescript compiler take care of checking that for you.
7179

72-
See [Guide to typescript-axios client template](../guides/client-templates/typescript-axios) for more details.
80+
See [Guide to typescript-axios client template](/guides/client-templates/typescript-axios) for more details.
7381
</Steps>
7482
</Tabs.Tab>
7583

@@ -81,7 +89,9 @@ You can check the version we develop and test against [here](https://github.com/
8189
npm i --dev @nahkies/openapi-code-generator @types/koa @types/koa__router
8290
npm i @nahkies/typescript-koa-runtime @koa/cors @koa/router koa koa-body zod
8391
```
84-
You could also install the CLI globally if you prefer, but it's best to version it per project.
92+
93+
You could also run the cli through `npx`, or install it globally if you prefer, but it's recommended to pin its
94+
version as a `devDependency` per project.
8595

8696
You can provide either a local file path or url for the input file.
8797

@@ -100,7 +110,40 @@ You can check the version we develop and test against [here](https://github.com/
100110

101111
By default the runtime validation is using zod.
102112

103-
See [Guide to typescript-koa client template](../guides/server-templates/typescript-koa) for more details.
113+
See [Guide to typescript-koa client template](/guides/server-templates/typescript-koa) for more details.
114+
</Steps>
115+
</Tabs.Tab>
116+
117+
<Tabs.Tab>
118+
<Steps>
119+
{<h3>Install dependencies</h3>}
120+
First install the CLI and the required runtime packages to your project:
121+
```sh npm2yarn
122+
npm i --dev @nahkies/openapi-code-generator @types/express
123+
npm i @nahkies/typescript-express-runtime express zod
124+
```
125+
126+
You could also run the cli through `npx`, or install it globally if you prefer, but it's recommended to pin its
127+
version as a `devDependency` per project.
128+
129+
You can provide either a local file path or url for the input file.
130+
131+
{<h3>Run generation</h3>}
132+
This will generate the server router and validation logic to `./src/generated`
133+
```sh npm2yarn
134+
npm run openapi-code-generator \
135+
--input ./openapi.yaml \ # or https://example.com/openapi.{json,yaml}
136+
--output ./src/generated \
137+
--template typescript-express
138+
```
139+
140+
{<h3>Profit</h3>}
141+
Implement handlers for your server, and be confident that they match what the client expects. Everything
142+
will be strongly typed, so typos are surfaced at development time, not runtime.
143+
144+
By default the runtime validation is using zod.
145+
146+
See [Guide to typescript-express client template](/guides/server-templates/typescript-express) for more details.
104147
</Steps>
105148
</Tabs.Tab>
106149
</Tabs>
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
---
2+
title: typescript-express
3+
---
4+
5+
import {Tabs} from 'nextra/components'
6+
7+
# Using the `typescript-express` template
8+
The `typescript-express` template outputs scaffolding code that handles the following:
9+
10+
- Building an express [Router](https://expressjs.com/en/5x/api.html#express.router) instance with all routes in the openapi specification
11+
- Generating typescript types and runtime schema parsers for all request parameters/bodies and response bodies, using [zod](https://zod.dev/) or [joi](https://joi.dev/)
12+
- Generating types for route implementations that receive strongly typed, runtime validated inputs and outputs
13+
- (Optionally) Actually starting the server and binding to a port
14+
15+
See [integration-tests/typescript-express](https://github.com/mnahkies/openapi-code-generator/tree/main/integration-tests/typescript-express) for more samples.
16+
17+
18+
### Install dependencies
19+
First install the CLI and the required runtime packages to your project:
20+
```sh npm2yarn
21+
npm i --dev @nahkies/openapi-code-generator @types/express
22+
npm i @nahkies/typescript-express-runtime express zod
23+
```
24+
25+
See also [quick start](../../getting-started/quick-start) guide
26+
27+
### Run generation
28+
<Tabs items={["OpenAPI3", "Typespec"]}>
29+
30+
<Tabs.Tab>
31+
```sh npm2yarn
32+
npm run openapi-code-generator \
33+
--input ./openapi.yaml \
34+
--input-type openapi3 \
35+
--output ./src/generated \
36+
--template typescript-express \
37+
--schema-builder zod
38+
```
39+
</Tabs.Tab>
40+
<Tabs.Tab>
41+
```sh npm2yarn
42+
npm run openapi-code-generator \
43+
--input ./typespec.tsp \
44+
--input-type typespec \
45+
--output ./src/generated \
46+
--template typescript-express \
47+
--schema-builder zod
48+
```
49+
</Tabs.Tab>
50+
51+
</Tabs>
52+
53+
### Using the generated code
54+
Running the above will output three files into `./src/generated`:
55+
56+
- `generated.ts` - exports a `createRouter` and `bootstrap` function, along with associated types used to create your server
57+
- `models.ts` - exports typescript types for schemas
58+
- `schemas.ts` - exports runtime schema validators
59+
60+
Once generated usage should look something like this:
61+
62+
```typescript
63+
import {bootstrap, createRouter, CreateTodoList, GetTodoLists} from "../generated"
64+
65+
// Define your route implementations as async functions implementing the types
66+
// exported from generated.ts
67+
const createTodoList: CreateTodoList = async ({body}, respond) => {
68+
const list = await prisma.todoList.create({
69+
data: {
70+
// body is strongly typed and parsed at runtime
71+
name: body.name,
72+
},
73+
})
74+
75+
// the `respond` parameter is a strongly typed response builder that pattern matches status codes
76+
// to the expected response schema.
77+
// the response body is validated against the response schema/status code at runtime
78+
return respond.with200().body(dbListToApiList(list))
79+
}
80+
81+
const getTodoLists: GetTodoLists = async ({query}) => {
82+
// omitted for brevity
83+
}
84+
85+
// Starts a server listening on `port`
86+
bootstrap({
87+
router: createRouter({getTodoLists, createTodoList}),
88+
port: 8080,
89+
})
90+
```
91+
92+
### Multiple routers
93+
By default, a single router is generated, but for larger API surfaces this can become unwieldy.
94+
95+
You can split the generated routers by using the [--grouping-strategy](/reference/cli-options#--grouping-strategy-value-experimental)
96+
option to control the strategy to use for splitting output into separate files. Set to none for a single generated.ts file, one of:
97+
98+
- none: don’t split output, yield single generated.ts (default)
99+
- first-tag: group operations based on their first tag
100+
- first-slug: group operations based on their first route slug/segment
101+
102+
This can help to organize your codebase and separate concerns.
103+
104+
### Custom Express app
105+
106+
The provided `bootstrap` function has a limited range of options. For more advanced use-cases,
107+
such as `https` you will need to construct your own Express `app`, and mount the router returned by `createRouter`.
108+
109+
The only real requirement is that you provide body parsing middleware mounted before the `router` that places
110+
a parsed request body on the `req.body` property.
111+
112+
Eg:
113+
```typescript
114+
import {createRouter, CreateTodoList, GetTodoLists} from "../generated"
115+
116+
import express from "express"
117+
import https from "https"
118+
import fs from "fs"
119+
120+
const createTodoList: CreateTodoList = async ({body}, respond) => { /*omitted for brevity*/ }
121+
const getTodoLists: GetTodoLists = async ({query}, respond) => { /*omitted for brevity*/ }
122+
123+
const app = express()
124+
125+
// mount middleware to parse JSON request bodies onto `req.body`
126+
app.use(express.json())
127+
128+
// mount the generated router with our handler implementations injected
129+
const router = createRouter({getTodoLists, createTodoList})
130+
app.use(router)
131+
132+
// create the HTTPS server using the express app
133+
https
134+
.createServer(
135+
{
136+
key: fs.readFileSync("path/to/key.pem"),
137+
cert: fs.readFileSync("path/to/cert.pem"),
138+
},
139+
app
140+
)
141+
.listen(433)
142+
```
143+
144+
### Error Handling
145+
146+
Any errors thrown during the request processing will be wrapped in `ExpressRuntimeError` objects,
147+
and tagged with the `phase` the error was thrown.
148+
149+
```typescript
150+
class ExpressRuntimeError extends Error {
151+
cause: unknown // the originally thrown exception
152+
phase: "request_validation" | "request_handler" | "response_validation"
153+
}
154+
```
155+
156+
This allows for implementing catch-all error middleware for common concerns like failed request validation,
157+
or internal server errors.
158+
159+
Eg:
160+
```typescript
161+
import {ExpressRuntimeError} from "@nahkies/typescript-express-runtime/errors"
162+
import {Request, Response, NextFunction} from "express"
163+
164+
export async function genericErrorMiddleware(err: Error, req: Request, res: Response, next: NextFunction) {
165+
if (res.headersSent) {
166+
return next(err)
167+
}
168+
169+
// if the request validation failed, return a 400 and include helpful
170+
// information about the problem
171+
if (ExpressRuntimeError.isExpressError(err) && err.phase === "request_validation") {
172+
res.status(400).json({
173+
message: "request validation failed",
174+
meta: err.cause instanceof ZodError ? {issues: err.cause.issues} : {},
175+
})
176+
return
177+
}
178+
179+
// return a 500 and omit information from the response otherwise
180+
logger.error("internal server error", err)
181+
res.status(500).json({
182+
message: "internal server error",
183+
})
184+
}
185+
```
186+
187+
You can configure the error handler through the `bootstrap` function using the `errorHandler` argument,
188+
or simplify mount directly to the express `app` yourself.
189+
190+
### Escape Hatches - raw `req` / `res` handling
191+
For most JSON based API's you shouldn't need to reach for this, but there are sometime situations where the
192+
code generation tooling doesn't support something you need (see also [roadmap](/overview/roadmap) / [compatibility](/overview/compatibility)).
193+
194+
Eg: response headers are not yet supported.
195+
196+
To account for these situations, we pass the raw express `req` and `res` objects to your handler implementations,
197+
allowing you full control where its needed.
198+
```typescript
199+
const createTodoList: CreateTodoList = async ({body}, respond, req, res) => {
200+
res.setHeader("x-ratelimit-remaining", "100")
201+
// ...your implementation
202+
return respond.with200().body({ /* ... */ })
203+
}
204+
```
205+
206+
It's also possible to skip response processing if needed by returning `ExpressRuntimeResponse.Skip` from your implementation.
207+
This allows you take complete control of the response.
208+
```typescript
209+
const getProfileImage: GetProfileImage = async ({body}, respond, req, res) => {
210+
res.setHeader("x-ratelimit-remaining", "100")
211+
res.status(200).send(Buffer.from([/*some binary file*/]))
212+
213+
return ExpressRuntimeResponse.Skip
214+
}
215+
```
216+
217+
It should be seldom that these escape hatches are required, and overtime fewer and fewer situations will
218+
require them.

packages/documentation/src/app/guides/server-templates/typescript-koa/page.mdx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,18 @@ bootstrap({
9696
})
9797
```
9898

99+
### Multiple routers
100+
By default, a single router is generated, but for larger API surfaces this can become unwieldy.
101+
102+
You can split the generated routers by using the [--grouping-strategy](/reference/cli-options#--grouping-strategy-value-experimental)
103+
option to control the strategy to use for splitting output into separate files. Set to none for a single generated.ts file, one of:
104+
105+
- none: don’t split output, yield single generated.ts (default)
106+
- first-tag: group operations based on their first tag
107+
- first-slug: group operations based on their first route slug/segment
108+
109+
This can help to organize your codebase and separate concerns.
110+
99111
### Custom Koa app/config
100112

101113
The provided `bootstrap` function has a limited range of options. For more advanced use-cases,
@@ -174,3 +186,34 @@ export async function genericErrorMiddleware(ctx: Context, next: Next) {
174186
}
175187
}
176188
```
189+
190+
### Escape Hatches - raw `ctx` handling
191+
For most JSON based API's you shouldn't need to reach for this, but there are sometime situations where the
192+
code generation tooling doesn't support something you need (see also [roadmap](/overview/roadmap) / [compatibility](/overview/compatibility)).
193+
194+
Eg: response headers are not yet supported.
195+
196+
To account for these situations, we pass the raw koa `ctx` object to your handler implementations,
197+
allowing you full control where its needed.
198+
```typescript
199+
const createTodoList: CreateTodoList = async ({body}, respond, ctx) => {
200+
ctx.set("x-ratelimit-remaining", "100")
201+
// ...your implementation
202+
return respond.with200().body({ /* ... */ })
203+
}
204+
```
205+
206+
It's also possible to skip response processing if needed by returning `KoaRuntimeResponse.Skip` from your implementation.
207+
This allows you take complete control of the response.
208+
```typescript
209+
const getProfileImage: GetProfileImage = async ({body}, respond, ctx) => {
210+
ctx.set("x-ratelimit-remaining", "100")
211+
ctx.status =200
212+
ctx.body = Buffer.from([/*some binary file*/])
213+
214+
return KoaRuntimeResponse.Skip
215+
}
216+
```
217+
218+
It should be seldom that these escape hatches are required, and overtime fewer and fewer situations will
219+
require them.

packages/documentation/src/app/overview/about/page.mdx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ make the generated clients feel idiomatic, and as close to handwritten as possib
2424
Server templates handle the routing setup, request and response validation/serialization so that you
2525
can focus on the business logic.
2626

27-
- [typescript-koa](../guides/server-templates/typescript-koa)
27+
- [typescript-express](/guides/server-templates/typescript-express)
28+
- [typescript-koa](/guides/server-templates/typescript-koa)
2829

2930
### Client SDK Templates
3031
Client templates give you a strongly typed interface to your remote server calls, ensuring that you

0 commit comments

Comments
 (0)