Next.jsAPIBackendTutorial

Next.js API Routes: Creating Backend Endpoints

Learn how to create API routes in Next.js to build backend functionality. Master route handlers, request methods, and building RESTful APIs.

By Vibe Code Basics12 min read
Next.js API Routes: Creating Backend Endpoints

Next.js API Routes: Creating Backend Endpoints

Next.js makes it easy to build full-stack applications by allowing you to create API endpoints right alongside your frontend code. In this guide, we'll explore how to create and use API routes in Next.js.

If you're new to Next.js, start with our Getting Started with Next.js guide. For routing basics, check out our Routing guide.

What are API Routes?

API routes in Next.js are server-side functions that handle HTTP requests. They're perfect for:

  • Creating RESTful APIs
  • Handling form submissions
  • Building webhook endpoints
  • Integrating with databases
  • Creating authentication endpoints

Creating Your First API Route

In the App Router, API routes are created using route.js or route.ts files in the app directory.

app/
  api/
    hello/
      route.js

This creates an endpoint at /api/hello.

Basic GET Handler

// app/api/hello/route.js
export async function GET() {
  return Response.json({ message: 'Hello from Next.js!' });
}

Handling Different HTTP Methods

You can export functions for different HTTP methods:

// app/api/users/route.js
export async function GET(request) {
  // Handle GET request
  const users = [
    { id: 1, name: 'John Doe' },
    { id: 2, name: 'Jane Smith' },
  ];
  
  return Response.json({ users });
}

export async function POST(request) {
  // Handle POST request
  const body = await request.json();
  
  // Process the data
  const newUser = {
    id: Date.now(),
    ...body,
  };
  
  return Response.json({ user: newUser }, { status: 201 });
}

Working with Request Data

Reading Request Body

export async function POST(request) {
  const body = await request.json();
  // Use body data
  return Response.json({ received: body });
}

Reading Query Parameters

export async function GET(request) {
  const { searchParams } = new URL(request.url);
  const id = searchParams.get('id');
  
  return Response.json({ id });
}

Reading Headers

export async function GET(request) {
  const authorization = request.headers.get('authorization');
  
  if (!authorization) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 });
  }
  
  return Response.json({ message: 'Authorized' });
}

Dynamic API Routes

Just like pages, API routes can be dynamic:

app/
  api/
    users/
      [id]/
        route.js
// app/api/users/[id]/route.js
export async function GET(request, { params }) {
  const { id } = await params;
  
  // Fetch user by ID
  const user = { id, name: 'John Doe' };
  
  return Response.json({ user });
}

export async function PUT(request, { params }) {
  const { id } = await params;
  const body = await request.json();
  
  // Update user
  return Response.json({ user: { id, ...body } });
}

export async function DELETE(request, { params }) {
  const { id } = await params;
  
  // Delete user
  return Response.json({ message: 'User deleted' }, { status: 200 });
}

Error Handling

Always handle errors properly in your API routes:

export async function GET(request) {
  try {
    // Your logic here
    const data = await fetchData();
    return Response.json({ data });
  } catch (error) {
    return Response.json(
      { error: 'Internal Server Error' },
      { status: 500 }
    );
  }
}

Working with Databases

Here's an example of integrating with a database:

// app/api/posts/route.js
import { db } from '@/lib/database';

export async function GET() {
  try {
    const posts = await db.post.findMany();
    return Response.json({ posts });
  } catch (error) {
    return Response.json(
      { error: 'Failed to fetch posts' },
      { status: 500 }
    );
  }
}

export async function POST(request) {
  try {
    const body = await request.json();
    const post = await db.post.create({
      data: body,
    });
    return Response.json({ post }, { status: 201 });
  } catch (error) {
    return Response.json(
      { error: 'Failed to create post' },
      { status: 500 }
    );
  }
}

Authentication in API Routes

Here's a simple example of handling authentication:

// app/api/protected/route.js
export async function GET(request) {
  const token = request.headers.get('authorization');
  
  if (!token || !isValidToken(token)) {
    return Response.json(
      { error: 'Unauthorized' },
      { status: 401 }
    );
  }
  
  return Response.json({ message: 'Protected data' });
}

CORS Configuration

If you need to handle CORS:

export async function GET(request) {
  return Response.json(
    { data: 'Hello' },
    {
      headers: {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE',
        'Access-Control-Allow-Headers': 'Content-Type',
      },
    }
  );
}

Best Practices

  1. Validate input: Always validate and sanitize user input
  2. Handle errors: Use try-catch blocks and return appropriate status codes
  3. Use TypeScript: Add type safety to your API routes
  4. Rate limiting: Implement rate limiting for public APIs
  5. Environment variables: Use environment variables for sensitive data
  6. Response format: Keep your response format consistent

Real-World Example: Blog API

Here's a complete example of a blog API:

// app/api/blog/route.js
let posts = [
  { id: 1, title: 'First Post', content: 'Content here' },
];

export async function GET() {
  return Response.json({ posts });
}

export async function POST(request) {
  const { title, content } = await request.json();
  
  if (!title || !content) {
    return Response.json(
      { error: 'Title and content are required' },
      { status: 400 }
    );
  }
  
  const newPost = {
    id: posts.length + 1,
    title,
    content,
    createdAt: new Date().toISOString(),
  };
  
  posts.push(newPost);
  
  return Response.json({ post: newPost }, { status: 201 });
}

Testing API Routes

You can test your API routes using tools like:

  • curl: Command-line tool
  • Postman: GUI tool for API testing
  • Thunder Client: VS Code extension
  • Next.js built-in testing: Use fetch in your tests

Conclusion

API routes in Next.js provide a powerful way to build backend functionality without a separate server. They're perfect for building full-stack applications with a single framework.

Next Steps:

Happy coding! 🚀

Related Articles