Skip to content

Commit

Permalink
fix: bodyparser causes trpc request hang (#26)
Browse files Browse the repository at this point in the history
* chore: update packageManager declaration

- lockfile produced with more recent pnpm and version in package.json was fairly old

* docs: update readme with koa bodyparser usage example

* chore: fix typos, change example order, other minor edits

* chore: more minor readme edits

* fix: mutation hang when data stream consumed by parser

- fixes body parsing issue raised here: #24
- data stream gets consumed by body parser
- changed middleware to look for parsed body and make available where trpc expects it

* test: refactor and add bodyparser integration cases

* chore: rm unecessary ts options

- recently added but ultimately determined they arent required. one of them causes the build to fail

* docs: mention/link gh issue in body parser note
  • Loading branch information
BlairCurrey committed Mar 14, 2024
1 parent 6e64a5c commit 02e8f71
Show file tree
Hide file tree
Showing 6 changed files with 344 additions and 266 deletions.
47 changes: 30 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,9 @@

This is an adapter which allows you to mount tRPC onto a Koa server. This is similar to the [trpc/packages/server/src/adapters/express.ts](https://github.com/trpc/trpc/blob/next/packages/server/src/adapters/express.ts) adapter.

# How to add tRPC to a Koa server
# How to Add tRPC to a Koa Server

Initialize a tRPC router and pass into `createKoaMiddleware` with the following parameters:

- router (required): the trpc router
- createContext (optional): a function returning the trpc context. If defined, the type should be registered on trpc initialization as shown in an example below and the trpc docs: https://trpc.io/docs/context
- prefix (optional): what to prefix the trpc routes with, such as `/trpc`

In addition to these examples, see the implementations in [`./test/createKoaMiddleware`](https://github.com/BlairCurrey/trpc-koa-adapter/blob/master/test/createKoaMiddleware.test.ts).

Example:
Initialize a tRPC router and pass into `createKoaMiddleware` (along with other desired [options](#arguments)). Here is a minimal example:

```ts
import Koa from 'koa';
Expand All @@ -31,29 +23,46 @@ const trpcRouter = trpc.router({
.output(Object)
.query((req) => {
return ALL_USERS.find((user) => req.input === user.id);
})
}),
});

const app = new Koa();
const adapter = createKoaMiddleware({
router: trpcRouter,
prefix: '/trpc'
prefix: '/trpc',
});
app.use(adapter);
app.listen(4000);
```

You can now reach the endpoint with:

```sh
curl -X GET "http://localhost:4000/trpc/user?input=1" -H 'content-type: application/json'
```

Returns:

Returns:

```json
{ "id": 1, "name": "bob" }
```

Using the context:
# createKoaMiddleware Arguments <a name="arguments"></a>

The middleware takes a configuration object with the following properties:

| Option | Required | Description |
| -------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| router | Required | The tRPC router to mount |
| createContext | Optional | A function returning the tRPC context. If defined, the type should be registered on tRPC initialization as shown in an example below and the tRPC docs: https://trpc.io/docs/context |
| prefix | Optional | The prefix for tRPC routes, such as `/trpc` |
| `nodeHTTPRequestHandler` options | Optional | Any of the options used by the underlying request handler. See tRPC's [nodeHTTPRequestHandler](https://github.com/trpc/trpc/blob/next/packages/server/src/adapters/node-http/nodeHTTPRequestHandler.ts) for more details |

# More examples

In addition to these examples, see the implementations in [`./test/createKoaMiddleware.test.ts`](https://github.com/BlairCurrey/trpc-koa-adapter/blob/master/test/createKoaMiddleware.test.ts).

## Using the Context:

```ts
const createContext = async ({ req, res }: CreateTrpcKoaContextOptions) => {
Expand All @@ -80,7 +89,7 @@ const trpcRouter = trpc.router({
ALL_USERS.push(newUser);

return newUser;
})
}),
});

const adapter = createKoaMiddleware({
Expand All @@ -90,6 +99,10 @@ const adapter = createKoaMiddleware({
});
```

# Note About Using With a Body Parser:

Using a bodyparser such as [`@koa/bodyparser`](https://github.com/koajs/bodyparser), [`koa-bodyparser`](https://www.npmjs.com/package/koa-bodyparser), or otherwise parsing the body will consume the data stream on the incoming request. To ensure that tRPC can handle the request, this library looks for the parsed body on `ctx.request.body`, which is where `@koa/bodyparser` and `koa-bodyparser` store the parsed body. If for some reason the parsed body is being stored somewhere else, and you need to parse the body before this middleware, the body will not be available to tRPC and mutations will fail as detailed [in this github issue](https://github.com/BlairCurrey/trpc-koa-adapter/issues/24).

# Development

The project uses `pnpm` for package management.
Expand All @@ -102,4 +115,4 @@ To get started clone the repo, install packages, build, and ensure tests pass:
pnpm build
pnpm test

Git commit messages must follow [conventional commit standard](https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional) (enforced by husky hooks). Versioning is handle by github actions and is determined by commit messages according to the [semantic-release](https://github.com/semantic-release/semantic-release#commit-message-format) rules and [`.releaserc`](.releaserc) configuration.
Git commit messages must follow [conventional commit standard](https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional) (enforced by husky hooks). Versioning is handle by github actions and is determined by commit messages according to the [semantic-release](https://github.com/semantic-release/semantic-release#commit-message-format) rules and [`.releaserc`](.releaserc) configuration.
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,17 @@
"middleware",
"typescript"
],
"packageManager": "pnpm@7.19.0",
"packageManager": "pnpm@8.15.4",
"devDependencies": {
"@commitlint/cli": "^17.6.6",
"@commitlint/config-conventional": "^17.6.6",
"@koa/bodyparser": "^5.0.0",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"@trpc/server": "^10.33.0",
"@types/jest": "^29.5.2",
"@types/koa": "^2.13.6",
"@types/koa-bodyparser": "^4.3.12",
"@types/supertest": "^2.0.12",
"@typescript-eslint/eslint-plugin": "^5.60.1",
"@typescript-eslint/parser": "^5.60.1",
Expand All @@ -46,6 +48,7 @@
"husky": "^8.0.3",
"jest": "^29.5.0",
"koa": "^2.14.2",
"koa-bodyparser": "^4.4.1",
"lint-staged": "^13.2.3",
"prettier": "^2.8.8",
"semantic-release": "^21.0.6",
Expand Down
98 changes: 98 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,23 @@ import {
import { Middleware } from 'koa';
import { IncomingMessage, ServerResponse } from 'http';

declare module 'koa' {
interface Request {
/* eslint-disable @typescript-eslint/no-explicit-any */
body?: any;
}
}
declare module 'http' {
interface IncomingMessage {
/* eslint-disable @typescript-eslint/no-explicit-any */
body?: any;
}
interface ServerResponse {
/* eslint-disable @typescript-eslint/no-explicit-any */
body?: any;
}
}

export type CreateTrpcKoaContextOptions = NodeHTTPCreateContextFnOptions<
IncomingMessage,
ServerResponse<IncomingMessage>
Expand All @@ -27,6 +44,13 @@ export const createKoaMiddleware =

if (prefix && !request.path.startsWith(prefix)) return next();

// put parsed body (by koa-bodyparser/@koa/bodyparser for example)
// where nodeHTTPRequestHandler will look for it.
// https://github.com/BlairCurrey/trpc-koa-adapter/issues/24
if ('body' in request) {
req.body = request.body;
}

// koa uses 404 as a default status but some logic in
// nodeHTTPRequestHandler assumes default status of 200.
// https://github.com/trpc/trpc/blob/abc941152b71ff2d68c63156eb5a142174779261/packages/server/src/adapters/node-http/nodeHTTPRequestHandler.ts#L63
Expand Down
Loading

0 comments on commit 02e8f71

Please sign in to comment.