Skip to content

Commit 6a8a72d

Browse files
committed
fix: Update response handling and improve body validation
- Removed metadata schema from GET route and adjusted response structure. - Changed response handling in POST route to use `NextResponse` and set a custom status. - Added tests for body validation scenarios, ensuring proper error handling when no body is provided. - Enhanced error handling in `RouteHandlerBuilder` for invalid body parsing.
1 parent 8fce7c1 commit 6a8a72d

File tree

3 files changed

+69
-19
lines changed

3 files changed

+69
-19
lines changed

README.md

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,21 +56,14 @@ const bodySchema = z.object({
5656
field: z.string(),
5757
});
5858

59-
const metadataSchema = z.object({
60-
permission: z.string(),
61-
role: z.enum(['admin', 'user']),
62-
});
63-
6459
export const GET = createZodRoute()
6560
.params(paramsSchema)
6661
.query(querySchema)
67-
.defineMetadata(metadataSchema)
6862
.handler((request, context) => {
6963
const { id } = context.params;
7064
const { search } = context.query;
71-
const { permission, role } = context.metadata!;
7265

73-
return Response.json({ id, search, permission, role }), { status: 200 };
66+
return { id, search, permission, role };
7467
});
7568

7669
export const POST = createZodRoute()
@@ -83,7 +76,8 @@ export const POST = createZodRoute()
8376
const { search } = context.query;
8477
const { field } = context.body;
8578

86-
return Response.json({ id, search, field }), { status: 200 };
79+
// Custom status
80+
return NextResponse.json({ id, search, field }), { status: 400 };
8781
});
8882
```
8983

@@ -111,7 +105,7 @@ You can return responses in two ways:
111105
1. **Return a Response object directly:**
112106

113107
```ts
114-
return Response.json({ data: 'value' }, { status: 200 });
108+
return NextResponse.json({ data: 'value' }, { status: 200 });
115109
```
116110

117111
2. **Return a plain object** that will be automatically converted to a JSON response with status 200:
@@ -122,6 +116,19 @@ return { data: 'value' };
122116

123117
## Advanced Usage
124118

119+
## Create client
120+
121+
You can create a reusable client in a file, I recommend `/src/lib/route.ts` with the following content:
122+
123+
```tsx
124+
import { createZodRoute } from 'next-zod-route';
125+
126+
const route = createZodRoute();
127+
128+
// Create other re-usable route
129+
const authRoute = route.use(...)
130+
```
131+
125132
### Static Parameters with Metadata
126133

127134
Metadata enable you to add **static parameters** to the route, for example to give permissions list to our application.

src/routeHandlerBuilder.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,42 @@ describe('body validation', () => {
129129
expect(response.status).toBe(400);
130130
expect(data.message).toBe('Invalid body');
131131
});
132+
133+
it('should not error when body schema is defined but no body is sent in POST request', async () => {
134+
const POST = createZodRoute()
135+
.body(bodySchema)
136+
.handler(() => {
137+
// If we reach here, it means no validation error was thrown
138+
return Response.json({ success: true }, { status: 200 });
139+
});
140+
141+
const request = new Request('http://localhost/', {
142+
method: 'POST',
143+
// No body provided intentionally
144+
});
145+
const response = await POST(request, { params: Promise.resolve({}) });
146+
147+
expect(response.status).toBe(400); // Should fail with 400 since body is required but not provided
148+
const data = await response.json();
149+
expect(data.message).toBe('Invalid body');
150+
});
151+
152+
it('should return the value when no body schema is defined and no body is provided', async () => {
153+
const POST = createZodRoute().handler(() => {
154+
// If we reach here, it means no validation error was thrown
155+
return Response.json({ success: true }, { status: 200 });
156+
});
157+
158+
const request = new Request('http://localhost/', {
159+
method: 'POST',
160+
// No body provided intentionally
161+
});
162+
const response = await POST(request, { params: Promise.resolve({}) });
163+
164+
expect(response.status).toBe(200); // Should fail with 400 since body is required but not provided
165+
const data = await response.json();
166+
expect(data.success).toBe(true);
167+
});
132168
});
133169

134170
describe('combined validation', () => {

src/routeHandlerBuilder.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -168,15 +168,21 @@ export class RouteHandlerBuilder<
168168
// Support both JSON and FormData parsing
169169
let body: unknown = {};
170170
if (request.method !== 'GET' && request.method !== 'DELETE') {
171-
const contentType = request.headers.get('content-type') || '';
172-
if (
173-
contentType.includes('multipart/form-data') ||
174-
contentType.includes('application/x-www-form-urlencoded')
175-
) {
176-
const formData = await request.formData();
177-
body = Object.fromEntries(formData.entries());
178-
} else {
179-
body = await request.json();
171+
try {
172+
const contentType = request.headers.get('content-type') || '';
173+
if (
174+
contentType.includes('multipart/form-data') ||
175+
contentType.includes('application/x-www-form-urlencoded')
176+
) {
177+
const formData = await request.formData();
178+
body = Object.fromEntries(formData.entries());
179+
} else {
180+
body = await request.json();
181+
}
182+
} catch (error) {
183+
if (this.config.bodySchema) {
184+
throw new InternalRouteHandlerError(JSON.stringify({ message: 'Invalid body', errors: error }));
185+
}
180186
}
181187
}
182188

@@ -204,6 +210,7 @@ export class RouteHandlerBuilder<
204210

205211
// Validate the body against the provided schema
206212
if (this.config.bodySchema) {
213+
console.log('body', body);
207214
const bodyResult = this.config.bodySchema.safeParse(body);
208215
if (!bodyResult.success) {
209216
throw new InternalRouteHandlerError(

0 commit comments

Comments
 (0)