Skip to content

Commit 6674f3f

Browse files
committed
refactor(next): improve route handling and add comprehensive method support
1 parent ebe7f3d commit 6674f3f

File tree

2 files changed

+84
-12
lines changed

2 files changed

+84
-12
lines changed

packages/api/src/next/route.ts

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import type {
1313
import type { Middleware } from '../compose.js';
1414
import { noop } from './noop.js';
1515

16-
// extract the path params from the urlq
16+
// extract the path params from the url
1717
type PathParams<_U extends string> = Record<string, string | string[]>;
1818

1919
type Handler<
@@ -53,23 +53,32 @@ type Handler<
5353
): Handler<I, OO, M, U, S, T, C>;
5454
};
5555

56-
type HandleBuilder<M extends string, C extends Record<string, unknown>> = <
57-
U extends string,
58-
>(
59-
url: U,
60-
initialConfig?: C,
61-
) => Handler<unknown, unknown, M, U, never, never, C>;
56+
type HandleBuilder<M extends string, C extends Record<string, unknown>> = {
57+
<U extends string, IC extends C = C>(
58+
url: U,
59+
initialConfig?: IC,
60+
): Handler<unknown, unknown, M, U, never, never, IC>;
61+
<IC extends C = C>(
62+
initialConfig?: IC,
63+
): Handler<unknown, unknown, M, '', never, never, IC>;
64+
};
6265

6366
export type NextRouteRequest = HttpApiRequest & {
6467
rawRequest: NextRequest;
65-
params: Record<string, string | string[]>;
68+
pathParams: Record<string, string | string[]>;
6669
};
6770

6871
export class NextRoute<
6972
Req extends NextRouteRequest,
7073
Ctx extends ApiContext,
7174
> extends BaseApi<Req, Ctx> {
75+
declare get: HandleBuilder<'GET', HttpApiConfig<Req>>;
7276
declare post: HandleBuilder<'POST', HttpApiConfig<Req>>;
77+
declare put: HandleBuilder<'PUT', HttpApiConfig<Req>>;
78+
declare patch: HandleBuilder<'PATCH', HttpApiConfig<Req>>;
79+
declare delete: HandleBuilder<'DELETE', HttpApiConfig<Req>>;
80+
declare head: HandleBuilder<'HEAD', HttpApiConfig<Req>>;
81+
declare options: HandleBuilder<'OPTIONS', HttpApiConfig<Req>>;
7382

7483
constructor(opts: {
7584
middlewares?: Middleware<Ctx, any>[];
@@ -79,10 +88,32 @@ export class NextRoute<
7988
middlewares: opts.middlewares ?? [],
8089
handleError: opts.handleError,
8190
});
91+
const allMethods = [
92+
'get',
93+
'post',
94+
'put',
95+
'patch',
96+
'delete',
97+
'head',
98+
'options',
99+
];
100+
for (const method of allMethods) {
101+
this[method] = (...args: any[]) => {
102+
const url = typeof args[0] === 'string' ? args[0] : '';
103+
const initialConfig = typeof args[0] === 'object' ? args[0] : args[1];
104+
return this.createNextRouteHandler({
105+
initialConfig: {
106+
...initialConfig,
107+
method: method.toUpperCase(),
108+
url,
109+
},
110+
});
111+
};
112+
}
82113
}
83114

84115
private async createRequest(
85-
initialConfig: HttpRequest & {},
116+
initialConfig: HttpRequest,
86117
req: NextRequest,
87118
route: { params: Record<string, string | string[]> },
88119
) {
@@ -92,7 +123,10 @@ export class NextRoute<
92123

93124
// form data or json
94125
let input: any;
95-
if (req.body) {
126+
127+
if (method === 'GET') {
128+
input = Object.fromEntries(req.nextUrl.searchParams);
129+
} else if (req.body) {
96130
const contentType = req.headers.get('content-type');
97131
if (contentType === 'application/json') {
98132
input = await req.json();
@@ -108,15 +142,15 @@ export class NextRoute<
108142
...initial,
109143
input,
110144
parsedInput: undefined,
111-
params: route.params,
145+
pathParams: route?.params,
112146
rawRequest: req,
113147
method,
114148
url,
115149
} as Req;
116150
}
117151

118152
private createNextRouteHandler(handlerOpts: {
119-
initialConfig: HttpApiConfig<Req> & HttpRequest;
153+
initialConfig: Partial<HttpApiConfig<Req>> & HttpRequest;
120154
schema?: Transform;
121155
transform?: Transform;
122156
action?: AnyAsyncFn;
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { describe, expect, it } from 'bun:test';
2+
import { NextRoute } from '../src/next/route.js';
3+
import { z } from 'zod';
4+
import { NextRequest, NextResponse } from 'next/server.js';
5+
6+
describe('Next.js Route', () => {
7+
it('should be able to create a route', async () => {
8+
const route = new NextRoute({});
9+
10+
const GET = route
11+
.get({ action: 'getUsers' })
12+
.validator(
13+
z.object({
14+
page: z.coerce.number().optional().default(1),
15+
limit: z.coerce.number().optional().default(10),
16+
}),
17+
)
18+
.action(async ({ req }) => {
19+
return NextResponse.json({
20+
users: [],
21+
page: req.parsedInput.page,
22+
limit: req.parsedInput.limit,
23+
});
24+
});
25+
26+
const request = new NextRequest('http://localhost:3000/users?limit=20', {
27+
method: 'GET',
28+
});
29+
30+
const response = await GET(request);
31+
expect(response.ok).toBe(true);
32+
expect(response.json()).resolves.toEqual({
33+
users: [],
34+
page: 1,
35+
limit: 20,
36+
});
37+
});
38+
});

0 commit comments

Comments
 (0)