|
| 1 | +import {Tabs} from 'nextra/components' |
| 2 | + |
| 3 | +# Using the `typescript-nextjs` template |
| 4 | + |
| 5 | +> ⚠️ **Alpha Template** ⚠️ |
| 6 | +> |
| 7 | +> This template is currently in **alpha**. APIs and features are subject to change.<br/> |
| 8 | +> It might break in unexpected ways, or **mangle** your code. |
| 9 | +> |
| 10 | +> You can see an example of it used on a real project here: <br/> |
| 11 | +> https://github.com/mnahkies/spdx-dependency-track |
| 12 | +
|
| 13 | + |
| 14 | +The `typescript-nextjs` template outputs scaffolding code that handles the following: |
| 15 | + |
| 16 | +- Generates route handlers in the Next.js App Router (app/api/.../route.ts) for every operation in your OpenAPI spec |
| 17 | +- Parses and validates request input (query, params, headers, and body) using `zod`, or `joi` |
| 18 | +- Validates response types at runtime before sending them, ensuring they conform to your OpenAPI spec |
| 19 | +- Enforces full type safety for each handler’s inputs and outputs |
| 20 | +- Additionally, emits a [typescript-fetch](../client-templates/typescript-fetch) client for making requests to the routes from your react code |
| 21 | + |
| 22 | +See [integration-tests/typescript-nextjs](https://github.com/mnahkies/openapi-code-generator/tree/main/integration-tests/typescript-nextjs) for more samples. |
| 23 | + |
| 24 | +### Install dependencies |
| 25 | +First install the CLI and the required runtime packages to your project: |
| 26 | +```sh npm2yarn |
| 27 | +npm i --dev @nahkies/openapi-code-generator |
| 28 | +npm i @nahkies/typescript-nextjs-runtime next zod |
| 29 | +``` |
| 30 | + |
| 31 | +See also [quick start](../../getting-started/quick-start) guide |
| 32 | + |
| 33 | +### Run generation |
| 34 | +<Tabs items={["OpenAPI3", "Typespec"]}> |
| 35 | + |
| 36 | + <Tabs.Tab> |
| 37 | + ```sh npm2yarn |
| 38 | + npm run openapi-code-generator \ |
| 39 | + --input ./openapi.yaml \ |
| 40 | + --input-type openapi3 \ |
| 41 | + --output ./src \ |
| 42 | + --template typescript-nextjs \ |
| 43 | + --schema-builder zod |
| 44 | + ``` |
| 45 | + </Tabs.Tab> |
| 46 | + <Tabs.Tab> |
| 47 | + ```sh npm2yarn |
| 48 | + npm run openapi-code-generator \ |
| 49 | + --input ./typespec.tsp \ |
| 50 | + --input-type typespec \ |
| 51 | + --output ./src \ |
| 52 | + --template typescript-nextjs \ |
| 53 | + --schema-builder zod |
| 54 | + ``` |
| 55 | + </Tabs.Tab> |
| 56 | + |
| 57 | +</Tabs> |
| 58 | + |
| 59 | +### Using the generated code |
| 60 | +Running the above will output a bunch of files into `./src`. Here's an example of the files output for a todo-list api specification: |
| 61 | +```shell |
| 62 | +src |
| 63 | +├── app |
| 64 | +│ ├── api |
| 65 | +│ │ └── list |
| 66 | +│ │ ├── [listId] |
| 67 | +│ │ │ ├── items |
| 68 | +│ │ │ │ └── route.ts |
| 69 | +│ │ │ └── route.ts |
| 70 | +│ │ └── route.ts |
| 71 | +│ ├── layout.tsx |
| 72 | +│ └── page.tsx |
| 73 | +└── generated |
| 74 | + ├── api |
| 75 | + │ └── list |
| 76 | + │ ├── [listId] |
| 77 | + │ │ ├── items |
| 78 | + │ │ │ └── route.ts |
| 79 | + │ │ └── route.ts |
| 80 | + │ └── route.ts |
| 81 | + ├── client.ts |
| 82 | + ├── models.ts |
| 83 | + └── schemas.ts |
| 84 | +```` |
| 85 | + |
| 86 | +`./src/app/../route.ts` |
| 87 | +- a `route.ts` file is generated per operation, following Next.js App Router conventions |
| 88 | +- exports handlers (GET, POST, etc.) for each HTTP method defined |
| 89 | +- safe to edit, your route handler implementations go here |
| 90 | +- calls into `./src/generated/...` for input/output validation logic |
| 91 | + |
| 92 | +`./src/generated/../route.ts` |
| 93 | +- mirror structure of the `./src/app/.../route.ts` files |
| 94 | +- contains glue code that parses input, validates responses, and calls your implementation |
| 95 | + |
| 96 | +`./src/generated/models.ts` |
| 97 | +- exports plain TypeScript types for all schemas in your OpenAPI spec |
| 98 | + |
| 99 | +`./src/generated/schemas.ts` |
| 100 | +- exports runtime schema validators (`zod` / `joi` depending on configuration) |
| 101 | + |
| 102 | +`./src/generated/client.ts` |
| 103 | +- exports a [typescript-fetch](../client-templates/typescript-fetch) client for calling your API from frontend code |
| 104 | +- see [use-with-react-query](../use-with-react-query) for integration with `react-query` |
| 105 | + |
| 106 | +#### Implementing routes |
| 107 | + |
| 108 | +Once generated usage should look something like this: |
| 109 | + |
| 110 | +```typescript |
| 111 | +import {db} from "../../../../../db" |
| 112 | +import { |
| 113 | + _GET, |
| 114 | + _POST, |
| 115 | +} from "../../../../../generated/todo-lists.yaml/list/[listId]/items/route" |
| 116 | +
|
| 117 | +export const GET = _GET(async ({params}, respond, request) => { |
| 118 | + const items = db.getTodoItems({listId: params.listId}) |
| 119 | +
|
| 120 | + if (items) { |
| 121 | + return respond.with200().body(items) |
| 122 | + } |
| 123 | + return respond |
| 124 | + .with404() |
| 125 | + .body({code: "not-found", message: `listId ${params.listId} not found`}) |
| 126 | +}) |
| 127 | +
|
| 128 | +export const POST = _POST(async ({params, body}, respond, request) => { |
| 129 | + await db.insertTodoItem({ |
| 130 | + listId: params.listId, |
| 131 | + itemId: body.id, |
| 132 | + content: body.content, |
| 133 | + completedAt: body.completedAt, |
| 134 | + }) |
| 135 | +
|
| 136 | + return respond.with204() |
| 137 | +}) |
| 138 | +
|
| 139 | +``` |
| 140 | +
|
| 141 | +#### Its safe to regenerate! |
| 142 | +The template uses [ts-morph](https://ts-morph.com/) to **non-destructively generate and update** route.ts files. |
| 143 | +
|
| 144 | +This means you can safely add your own logic to the scaffolded files, and future regenerations will preserve your |
| 145 | +implementation code while updating the generated boilerplate. |
| 146 | +
|
| 147 | +#### Error Handling |
| 148 | +
|
| 149 | +> 🚧 Under construction |
| 150 | +> |
| 151 | +> Errors will be thrown for req/res validation issues, but currently its impossible to catch them. |
| 152 | +> More thought is needed... |
| 153 | +
|
| 154 | +### Escape Hatches |
| 155 | +
|
| 156 | +> 🚧 Under construction |
| 157 | +> |
| 158 | +> The raw nextjs `request` object is passed to your implementation, however there is not yet |
| 159 | +> a way to skip response processing. |
| 160 | +
|
| 161 | +Most APIs won't need this, but in some cases (e.g. unsupported features), you can use escape hatches to drop out of |
| 162 | +the generated scaffolding. |
| 163 | +
|
| 164 | +For example, we pass the raw nextjs `request` object to your handler implementations, |
| 165 | +allowing you full control where its needed. |
| 166 | +```typescript |
| 167 | +export const GET = _GET(async ({params}, respond, request) => { |
| 168 | + console.log(request.nextUrl.buildId) |
| 169 | + // ...your implementation here |
| 170 | +}) |
| 171 | +``` |
| 172 | +
|
| 173 | +Use sparingly - the goal is to reduce the need for escape hatches over time. |
0 commit comments