Skip to content

Commit 5ccd433

Browse files
committed
write readme and fix export issue in the . endpoint
1 parent d5a664d commit 5ccd433

File tree

6 files changed

+252
-37
lines changed

6 files changed

+252
-37
lines changed

README.md

Lines changed: 208 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,209 @@
11
# next-trpc
2-
A simple typed rpc interface to easily type api endpoints in a app router nextjs application
2+
3+
A simple typed rpc interface to easily type api endpoints in an app router nextjs application.
4+
5+
## Installation
6+
7+
```sh
8+
npm install @creatorem/next-trpc
9+
```
10+
11+
<br/>
12+
13+
## Setup
14+
15+
### Create a router file
16+
17+
Contains your api endpoints.
18+
19+
`trpc/router.ts`
20+
21+
```ts
22+
import { router, endpoint } from "@creatorem/next-trpc";
23+
import z from "zod";
24+
25+
export const appRouter = router({
26+
getUser: endpoint.action(async ({ db }) => {
27+
return await myAsyncFunction();
28+
}),
29+
greeting: endpoint
30+
.input(
31+
// you can add zod typechecking to your entry params
32+
z.object({
33+
name: z.string(),
34+
age: z.coerce.number(),
35+
})
36+
)
37+
.action(({ name, age }) => {
38+
return `Hi my name is ${name}, and I am ${age} years old.`;
39+
}),
40+
});
41+
42+
export type AppRouter = typeof appRouter;
43+
```
44+
45+
### Connect your router to an api endpoint.
46+
47+
`app/api/trpc/[trpc]/route.ts`
48+
49+
```ts
50+
import { createTrpcAPI } from "@creatorem/next-trpc/server";
51+
import { appRouter } from "~/trpc/router";
52+
53+
const handler = createTrpcAPI({
54+
router: appRouter,
55+
});
56+
57+
export { handler as GET, handler as POST };
58+
```
59+
60+
### Start fetching with a type safe client!
61+
62+
`trpc/client.ts`
63+
64+
```ts
65+
import { envs } from "~/envs";
66+
import { createTrpcClient } from "@creatorem/next-trpc/client";
67+
import { type AppRouter } from "./router";
68+
69+
const url = envs().NEXT_PUBLIC_YOUR_APP_URL + "/api/trpc";
70+
71+
export const trpc = createTrpcClient<AppRouter>({
72+
url,
73+
headers: async () => {
74+
// add custom headers like Authorization to make it works with auth logic
75+
return {
76+
/* Authorization: `Bearer ${jwt!}` */
77+
};
78+
},
79+
});
80+
```
81+
82+
Done !
83+
84+
## Usage
85+
86+
Now you can use the `trpc` client and server side.
87+
88+
`app/layout.tsx`
89+
90+
```tsx
91+
import React from "react";
92+
import { redirect } from "next/navigation";
93+
import { trpc } from "~/trpc/client";
94+
95+
export default async function Layout(
96+
props: React.PropsWithChildren
97+
): Promise<React.JSX.Element> {
98+
const user = await trpc.getUser.fetch();
99+
100+
if (!user) {
101+
return redirect(/* path to login page */);
102+
}
103+
104+
return <>{props.children}</>;
105+
}
106+
```
107+
108+
### Integrated useQuery hook usage
109+
110+
We offer a client side only function to create client object that pre-implement the `useQuery` hook from `@tanstack/react-query` package.
111+
112+
You need to have `@tanstack/react-query` installed.
113+
114+
```sh
115+
npm install @tanstack/react-query
116+
```
117+
118+
Then you can create the following file :
119+
120+
`trpc/query-client.ts`
121+
122+
```ts
123+
import "client-only";
124+
125+
import { envs } from "~/envs";
126+
import { createTrpcQueryClient } from "@creatorem/next-trpc/query-client";
127+
import { type AppRouter } from "./router";
128+
129+
const url = envs().NEXT_PUBLIC_YOUR_APP_URL + "/api/trpc";
130+
131+
export const clientTrpc = createTrpcQueryClient<AppRouter>({
132+
url,
133+
headers: async () => {
134+
// add custom headers like Authorization to make it works with auth logic
135+
return {
136+
/* Authorization: `Bearer ${jwt!}` */
137+
};
138+
},
139+
});
140+
```
141+
142+
Now you can do :
143+
144+
```tsx
145+
"use client";
146+
147+
import React from "react";
148+
import { clientTrpc } from "~/trpc/query-client";
149+
150+
export const MyClientComponent: React.FC<React.PropsWithChildren> = (props) => {
151+
const { data: user } = clientTrpc.getUser.useQuery();
152+
/* ... */
153+
154+
return <>{props.children}</>;
155+
};
156+
```
157+
158+
## Use a router context
159+
160+
You can use a context object to pass data to all your endpoint callbacks.
161+
162+
`trpc/router.ts`
163+
164+
```ts {4-7,9}
165+
import { CtxRouter, endpoint } from "@creatorem/next-trpc";
166+
import z from "zod";
167+
168+
export const createContext = async () => {
169+
/* your own context logic here ... */
170+
return { db /* let's say db is a database client. */ };
171+
};
172+
173+
const ctx = new CtxRouter<Awaited<ReturnType<typeof createContext>>>();
174+
175+
export const appRouter = ctx.router({
176+
getUser: endpoint.action(async ({ db }) => {
177+
return await db.user.get();
178+
}),
179+
greeting: endpoint
180+
.input(
181+
// you can add zod typechecking to your entry params
182+
z.object({
183+
name: z.string(),
184+
age: z.coerce.number(),
185+
})
186+
)
187+
.action(({ name, age }) => {
188+
return `Hi my name is ${name}, and I am ${age} years old.`;
189+
}),
190+
});
191+
192+
export type AppRouter = typeof appRouter;
193+
```
194+
195+
Next pass your context to the nextjs api endpoint.
196+
197+
`app/api/trpc/[trpc]/route.ts`
198+
199+
```ts {6}
200+
import { createTrpcAPI } from "@creatorem/next-trpc/server";
201+
import { appRouter, createContext } from "~/trpc/router";
202+
203+
const handler = createTrpcAPI({
204+
router: appRouter,
205+
ctx: createContext,
206+
});
207+
208+
export { handler as GET, handler as POST };
209+
```

package.json

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
11
{
22
"name": "@creatorem/next-trpc",
3-
"private": false,
4-
"version": "1.0.1",
3+
"private": true,
4+
"type": "module",
5+
"version": "1.0.2",
6+
"repository": {
7+
"type": "git",
8+
"url": "git+https://github.com/creatorem/next-trpc"
9+
},
510
"homepage": "https://creatorem.com",
611
"author": {
712
"url": "https://creatorem.com",
813
"name": "Creatorem"
914
},
15+
"keywords": [
16+
"nextjs",
17+
"trpc",
18+
"typescript"
19+
],
1020
"license": "MIT",
1121
"publishConfig": {
1222
"access": "public"
@@ -25,21 +35,21 @@
2535
"test:coverage": "jest --coverage"
2636
},
2737
"exports": {
28-
"./": {
38+
".": {
2939
"require": "./dist/core.js",
3040
"types": "./dist/core.d.ts"
3141
},
3242
"./server": {
33-
"require": "./dist/create-rpc-api.js",
34-
"types": "./dist/create-rpc-api.d.ts"
43+
"require": "./dist/create-trpc-api.js",
44+
"types": "./dist/create-trpc-api.d.ts"
3545
},
3646
"./client": {
37-
"require": "./dist/create-rpc-client.js",
38-
"types": "./dist/create-rpc-client.d.ts"
47+
"require": "./dist/create-trpc-client.js",
48+
"types": "./dist/create-trpc-client.d.ts"
3949
},
4050
"./query-client": {
41-
"require": "./dist/create-rpc-query-client.js",
42-
"types": "./dist/create-rpc-query-client.d.ts"
51+
"require": "./dist/create-trpc-query-client.js",
52+
"types": "./dist/create-trpc-query-client.d.ts"
4353
}
4454
},
4555
"devDependencies": {

src/core.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export type Endpoint<
1414
} & Ctx
1515
) => Output
1616
: (
17-
input: Input extends Schema ? import("zod").infer<Input> : Input,
17+
input: Input extends Schema ? z.infer<Input> : Input,
1818
context: {
1919
request: NextRequest;
2020
} & Ctx
Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,41 +19,40 @@ const parseInput = (request: NextRequest, endpoint: Endpoint<any, any>) => {
1919
return endpoint.input.parse(paramsObj);
2020
};
2121

22-
export const createRpcAPI = <Ctx>({
22+
export const createTrpcAPI = <Ctx>({
2323
router,
2424
ctx,
2525
}: {
26-
// router: ReturnType<typeof routerFn>;
2726
router: ReturnType<typeof routerFn<any, Router<any>>>;
2827
ctx?: (request: NextRequest) => Promise<Ctx>;
2928
}) => {
3029
return async function handler(
3130
request: NextRequest,
3231
context: { params: Promise<unknown> }
3332
): Promise<NextResponse<unknown>> {
34-
const params = (await context.params) as { rpc: string };
33+
const params = (await context.params) as { trpc: string };
3534

36-
if (!("rpc" in params)) {
35+
if (!("trpc" in params)) {
3736
return NextResponse.json(
3837
{
3938
data: null,
40-
error: "You must call createAPI in a [rpc]/route.ts file.",
39+
error: "You must call createAPI in a [trpc]/route.ts file.",
4140
},
4241
{ status: 400 }
4342
);
4443
}
45-
if (!params.rpc) {
44+
if (!params.trpc) {
4645
return NextResponse.json(
4746
{
4847
data: null,
4948
error:
50-
"You must pass a params in your [rpc]/you-must-put-a-param-here call",
49+
"You must pass a params in your [trpc]/you-must-put-a-param-here call",
5150
},
5251
{ status: 400 }
5352
);
5453
}
5554

56-
const endpointAttribute = camelize(params.rpc);
55+
const endpointAttribute = camelize(params.trpc);
5756

5857
if (!(endpointAttribute in router) || !router[endpointAttribute]) {
5958
return NextResponse.json(
Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Router, type Endpoint, type router } from "./core";
22
import { kebabize } from "./utils";
33

4-
type RpcClient<R extends Router<any>> = {
4+
type TrpcClient<R extends Router<any>> = {
55
[K in keyof R]: R[K] extends Endpoint<infer Output, infer Input, any>
66
? Input extends undefined
77
? { fetch: () => Promise<Output> }
@@ -15,14 +15,14 @@ type RpcClient<R extends Router<any>> = {
1515
: never;
1616
};
1717

18-
export const getRpcFetch =
18+
export const getTrpcFetch =
1919
({
2020
endpointSlug,
2121
url,
2222
headers,
2323
}: {
2424
endpointSlug: string;
25-
} & CreateRpcClientOptions) =>
25+
} & createTrpcClientOptions) =>
2626
async (input?: any) => {
2727
const endpointName = kebabize(endpointSlug);
2828

@@ -55,22 +55,21 @@ export const getRpcFetch =
5555
return result.data;
5656
};
5757

58-
export interface CreateRpcClientOptions {
58+
export interface createTrpcClientOptions {
5959
url: string;
6060
headers: HeadersInit | (() => Promise<HeadersInit>);
6161
}
6262

63-
// export const createRpcClient = <R extends ReturnType<typeof router>>({
64-
export const createRpcClient = <
63+
export const createTrpcClient = <
6564
R extends ReturnType<typeof router<any, Router<any>>>
6665
>(
67-
opts: CreateRpcClientOptions
68-
): RpcClient<R> => {
69-
return new Proxy({} as RpcClient<R>, {
66+
opts: createTrpcClientOptions
67+
): TrpcClient<R> => {
68+
return new Proxy({} as TrpcClient<R>, {
7069
get(target, prop) {
7170
if (typeof prop === "string") {
7271
return {
73-
fetch: getRpcFetch({
72+
fetch: getTrpcFetch({
7473
endpointSlug: prop,
7574
...opts,
7675
}),

0 commit comments

Comments
 (0)