Skip to content

API Tips & Middleware

Subhadip Saha edited this page May 16, 2025 · 1 revision

This page serves as a practical guide for developers working on CodeNearby's backend routes. It explains best practices, usage tips, and how to properly use middleware to keep the API layer secure, efficient, and maintainable.

Folder Structure

CodeNearby uses the App Router (Next.js 14) with app/api/.../route.ts convention. Each route file exports GET, POST, PATCH, or DELETE handlers.

Example:

app/
└── api/
    └── friends/
        ├── request/
        │   └── [id]/
        │       └── route.ts
        └── requests/
            └── route.ts

API Route Patterns

  • Use RESTful principles for endpoints.
  • Group similar routes under the same folder.
  • Use dynamic routes (e.g., [id]) for resource access.

Example route signature:

export async function POST(req: Request) {
  // logic here
}

Middleware Usage

What It Is

Middleware runs before your actual API handler to perform:

  • Auth validation
  • Logging
  • Input sanitization
  • Rate limiting

How to Use

Create a utility (e.g., lib/middleware.ts) and wrap handlers:

export function withMiddleware(handler: Function) {
  return async (req: Request) => {
    // common logic
    return handler(req);
  };
}

Usage in route:

import { withMiddleware } from "@/lib/middleware";

export const GET = withMiddleware(async (req) => {
  // actual logic
});

Authentication Middleware

Use it to protect sensitive endpoints. For example:

import { getServerSession } from "next-auth";

export async function withAuth(req: Request) {
  const session = await getServerSession(authOptions);
  if (!session?.user) {
    return new Response("Unauthorized", { status: 401 });
  }
  return session;
}

Rate Limiting

Protect public endpoints (e.g., AI search, public posts) using Redis or in-memory counters.

Example strategy:

  • Limit to 10 requests/minute per IP
  • Use a key like rate-limit:<ip> in Redis
  • Return 429 status on overflow

Use a shared utility like:

import { isRateLimited } from "@/lib/rateLimiter";

if (await isRateLimited(ip)) {
  return new Response("Rate limit exceeded", { status: 429 });
}

Error Handling

Use consistent structure for API errors:

return new Response(JSON.stringify({
  success: false,
  error: "Invalid input"
}), { status: 400 });

Avoid generic 500 errors unless necessary. Log details internally, but return safe error messages.


Logging

Use console.log or a logger wrapper during development. For production, prefer structured logs.

Example:

console.log(`[POST /api/friends/request] user=${userId} friend=${friendId}`);

Response Format Guidelines

Standardize successful responses:

{
  "success": true,
  "data": {...}
}

Error responses:

{
  "success": false,
  "error": "Message here"
}

Always return proper HTTP status codes (e.g., 200, 201, 400, 401, 403, 500).


Common Pitfalls

  • Forgetting to handle OPTIONS for CORS
  • Not validating user sessions
  • Missing return on error branch
  • Overusing try-catch without logging
  • Nesting too many dynamic folders unnecessarily

For more examples, explore files under app/api/*/route.ts. Keep handlers stateless, secure, and deterministic.

Clone this wiki locally