keepcalculator

06 Numbers and Powers

Next.js Route Handlers: Why They Feel Like Flask

Card No. 06·15429 · Updated · Standard formula, shown below

Next.js route handlers give you a way to write server-side code directly inside your app directory, and for anyone coming from Flask, the pattern feels instantly familiar because both frameworks let you define an endpoint as a function that receives a request and returns a response. You write a function, export it with a specific name (GET, POST, etc.), and the framework wires it to a URL path for you. This is the same mental model Flask uses with @app.route and a function that returns a string or a response object.

Background: From Pages to Route Handlers

Next.js started as a React framework that only rendered pages. In the old pages directory, you could add server-side logic through API routes by creating a file in pages/api and exporting a default function. That worked, but it was separate from your UI code and had a different syntax.

With the App Router introduced in Next.js 13, the framework unified everything under the app directory. Now a single folder can contain a page.tsx for the UI and a route.ts for the API endpoint at the same URL. This is a big shift because it lets you co-locate server logic with the components that use it.

Route handlers are the evolution of API routes. They support more HTTP methods, can access the full request object, and are built on the Web Request and Response standards rather than Node.js-specific objects. That makes them work in any environment Next.js supports, including serverless platforms.

How Route Handlers Work

To create a route handler, you create a file named route.ts (or route.js) inside the app directory. The file exports named functions for each HTTP method you want to support: GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS. Each function receives a Request object and returns a Response or a NextResponse.

Here is a minimal example:

// app/api/hello/route.ts
export async function GET() {
  return Response.json({ message: "Hello, world!" })
}

This creates an endpoint at /api/hello that responds to GET requests with a JSON object. Notice you don’t need to specify the URL anywhere—the file path determines it. This is exactly how Flask’s decorator works, except the path is implicit in the file structure rather than written as a string.

For a POST request, you might read the body:

export async function POST(request: Request) {
  const body = await request.json()
  return Response.json({ received: body }, { status: 201 })
}

The request.json() method parses the JSON body, similar to Flask’s request.get_json(). The Response.json() helper sets the content-type header and serializes the object for you, so you don’t have to call json.dumps() manually.

Why It Feels Like Flask

Flask developers feel at home because the core pattern is identical: a function that receives a request and returns a response, with the HTTP method determining which function runs. In Flask, you write:

from flask import request, jsonify

@app.route("/api/hello", methods=["GET"])
def hello():
    return jsonify({"message": "Hello, world!"})

Next.js route handlers are the same idea, but the route is derived from the file path and the method is the function name. You don’t need to match URLs with decorators or worry about trailing slashes—the file system handles it.

Another similarity is the request object. Flask wraps the WSGI environ into a request object with methods like request.args for query parameters and request.form for form data. Next.js uses the standard Web API Request object, which has request.nextUrl.searchParams for query strings and request.formData() for forms. The names differ, but the pattern of accessing parsed data from the request is the same.

Response handling also mirrors Flask. In Flask, you return a string, a dict (with jsonify), or a Response object. In Next.js, you return a Response object, and the Response.json() static method is a shortcut for JSON. You can also return plain text with new Response("text") or set custom headers by passing a headers object.

Common Patterns and Differences

Despite the familiarity, there are key differences to keep in mind.

  • Dynamic segments: In Flask, you capture URL parameters with <int:user_id> in the route. In Next.js, you create a folder with square brackets, like app/users/[id]/route.ts, and access the param via the second argument: export async function GET(request: Request, { params }: { params: { id: string } }).
  • Middleware vs. decorators: Flask uses @app.before_request for global logic. Next.js uses middleware.ts at the project root for running code before a request reaches a route handler. It runs on the edge runtime and is more limited than a full Node.js environment.
  • Runtime: Route handlers can run on Node.js or the Edge runtime. The Edge runtime is lighter and faster but supports fewer Node.js APIs. Flask runs only on a traditional server, so you have full access to the filesystem and any Python library.
  • Rendering vs. API: In Next.js, route handlers are separate from page rendering. A page component can also be a server component that fetches data directly, without a route handler. In Flask, you typically render templates or return JSON from the same route function. Next.js gives you the option to separate concerns more cleanly.

One common misconception is that route handlers are only for building APIs. They can also be used for server-side actions like handling form submissions, webhooks, or even returning HTML fragments. But for most UI needs, you’ll use server components or Server Actions instead, which are more integrated with React.

How to Choose Between Route Handlers and Server Actions

Next.js offers two main ways to handle server-side logic: route handlers and Server Actions. Route handlers are best for:

  • Building a public API that external clients consume.
  • Handling webhooks from third-party services.
  • Creating endpoints that return non-JSON data (e.g., CSV, images).
  • When you need fine control over the HTTP response (status codes, headers, caching).

Server Actions are better for:

  • Mutations that happen as part of a form submission or button click.
  • When you want to revalidate cached data after a write.
  • When the logic is tightly coupled to a specific component.

If you’re building a form on your own page, a Server Action is usually simpler. If you’re building an API that another app will call, a route handler is the right choice. Flask developers will find route handlers more natural because they map directly to the Flask view functions they’re used to.

Common Problems and Pitfalls

Here are a few issues that trip up developers new to route handlers.

  • Forgetting to export the right method: If you export a function named GET and the client sends a POST, you’ll get a 405 Method Not Allowed. Make sure you export all methods you need.
  • Not handling the request body correctly: If you try to call request.json() on a GET request with no body, it will throw an error. Always check the method or wrap in try/catch.
  • Relying on Node.js APIs in Edge runtime: If you set export const runtime = 'edge', you can’t use fs or path. Stick to Web APIs or use the Node.js runtime (the default).
  • Path conflicts: If you have both page.tsx and route.ts in the same folder, Next.js will throw an error. You can’t have both a UI page and an API route at the same URL.
  • Cache issues: By default, GET route handlers are cached in some cases (when using the Edge runtime or when the response is static). If you need fresh data every time, set export const dynamic = 'force-dynamic' or use request.nextUrl.searchParams to make it dynamic.

Frequently Asked Questions

Can I use route handlers with the Pages Router?

No. Route handlers are a feature of the App Router. If you’re using the Pages Router, you should use API routes (pages/api), which have a similar but slightly different syntax. API routes export a single default function that receives req and res objects, not separate functions per method.

How do I access query parameters in a route handler?

Use request.nextUrl.searchParams. For example, const name = request.nextUrl.searchParams.get('name'). This returns a URLSearchParams object, so you can also iterate over all params or get multiple values.

Can I return HTML from a route handler?

Yes. You can return a Response with a content-type of text/html. For example: new Response('<h1>Hello</h1>', { headers: { 'Content-Type': 'text/html' } }). However, for rendering React components, you should use server components or a page, not a route handler.

Are route handlers server-side only?

Yes. Route handlers always run on the server. They never run in the browser. That means you can safely put database credentials, API keys, and other secrets in them, as long as you don’t expose them in the response.

How do I handle CORS in route handlers?

You need to set the appropriate headers manually. For example, to allow all origins, add Access-Control-Allow-Origin: * to your response headers. For preflight requests, you can export an OPTIONS handler that returns a response with the allowed methods and headers.

Can I use route handlers for file uploads?

Yes. Use request.formData() to get the uploaded files. The returned FormData object has a get() method that returns a File object. You can then read the file content with file.arrayBuffer() and store it or process it as needed.

Alternatives and When to Use Them

If you’re building a full API, you might consider a dedicated backend framework like Flask, FastAPI, or Express. Next.js route handlers are great for simple endpoints and for integrating tightly with your frontend, but they lack some features you might need for a large API:

  • Built-in validation: Flask with marshmallow or FastAPI with Pydantic gives you request validation out of the box. In Next.js, you’d need to write your own validation logic.
  • OpenAPI documentation: FastAPI auto-generates OpenAPI docs. Next.js doesn’t have that built-in, though you can add tools like next-swagger-doc.
  • Database migrations: Flask and Django have mature migration tools. Next.js doesn’t include any, so you’d use Prisma or another ORM.

For a simple API that serves your own frontend, route handlers are often enough. For a public API with complex business logic, a dedicated backend might be better. You can also mix both: use Next.js for the frontend and a separate backend for the API, but that adds complexity.

Ultimately, the reason route handlers feel like Flask is that they follow the same request-response cycle that web developers have used for decades. It’s a proven pattern, and Next.js has adopted it in a way that fits seamlessly into a React application. If you already know Flask, you already know most of what you need to build APIs with Next.js.

Related

Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.

More cards in this drawer

The whole Numbers and Powers drawer →