DEV Community

Cover image for Mastering Next.js 13/14 : App Router and API Routes
Fabrikapp
Fabrikapp

Posted on

Mastering Next.js 13/14 : App Router and API Routes

Next.js 13 introduced a new routing system called the App Router, which provides a more flexible and intuitive way to define routes and handle navigation in your application.

What is the App Router ?

The App Router is built on top of React Server Components and introduces new concepts and conventions for defining routes and handling navigation.

With the App Router, you define your routes using a directory structure inside the app directory. Each directory represents a route segment, and the files inside those directories define the UI and behavior for that route.

Here's an example of a basic App Router structure:

app/
  dashboard/
    layout.js
    page.js
  settings/
    layout.js
    page.js
  layout.js
  page.js
Enter fullscreen mode Exit fullscreen mode

In this example, we have two routes: /dashboard and /settings. Each route has its own layout.js and page.js files. The layout.js file defines the common UI layout for that route, while the page.js file defines the specific UI for that route.

Migrating from Pages Router to App Router

If you have an existing Next.js application using the Pages Router, you can migrate to the App Router incrementally. The App Router is available in Next.js 13 and later versions and can coexist with the Pages Router.

To start migrating, you can create an app directory at the root of your project and gradually move your routes and components into it. You can have a mix of Pages Router and App Router in your application during the migration process.

Here's an example of a migrated route:

Before :

pages/
  dashboard.js
Enter fullscreen mode Exit fullscreen mode

After :

app/
  dashboard/
    page.js
Enter fullscreen mode Exit fullscreen mode

In this example, the /dashboard route is migrated to the App Router by creating a dashboard directory inside the app directory and moving the route's logic into the page.js file.

File-based Routing

With the App Router, routes are defined using a file-based convention. Each file inside the app directory represents a route segment, and the directory structure determines theroute hierarchy.

Here's an example of a more complex App Router structure:

app/
  blog/
    [slug]/
      page.js
    page.js
  dashboard/
    settings/
      page.js
    page.js
  layout.js
  page.js
Enter fullscreen mode Exit fullscreen mode

In this example, we have the following routes:

  • /: Defined by app/page.js
  • /blog: Defined by app/blog/page.js
  • /blog/[slug]: Defined by app/blog/[slug]/page.js, where [slug] is a dynamic segment
  • /dashboard: Defined by app/dashboard/page.js
  • /dashboard/settings: Defined by app/dashboard/settings/page.js

The layout.js file at the root level defines the common UI layout for all routes.

Layouts and Nesting

The App Router introduces the concept of layouts, which allow you to define shared UI across multiple routes. Layouts are defined using the layout.js file in each route directory.

Here's an example of a layout file:

// app/dashboard/layout.js
import Sidebar from './Sidebar';

export default function DashboardLayout({ children }) {
  return (
    <div>
      <Sidebar />
      <main>{children}</main>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

In this example, the DashboardLayout component defines the common UI structure for all routes within the /dashboard route. The children prop represents the content of the nested routes.

Layouts can be nested, allowing you to create a hierarchy of shared UI components. For example:

app/
  dashboard/
    settings/
      page.js
    layout.js
    page.js
  layout.js
Enter fullscreen mode Exit fullscreen mode

In this case, the app/dashboard/layout.js file defines the layout for the /dashboard route, and the app/layout.js file defines the root layout for all routes.

Data Fetching with the App Router

The App Router provides a new data fetching approach using React Server Components. You can fetch data directly in your components using the fetch function or other data fetching libraries.

Here's an example of data fetching in a page component:

// app/blog/[slug]/page.js
async function getPost(slug) {
  const res = await fetch(`https://api.example.com/posts/${slug}`);
  return res.json();
}

export default async function PostPage({ params }) {
  const post = await getPost(params.slug);

  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

In this example, the getPost function fetches a blog post from an API based on the slug parameter. The PostPage component is an async function that awaits the data fetching and renders the post.

The App Router automatically handles data fetching on the server and sends the pre-rendered HTML to the client, providing a fast initial load experience.

6. API Routes

Next.js allows you to create API routes, which are server-side endpoints that can handle HTTP requests and perform server-side logic.

Creating API Routes

To create an API route, you need to create a file inside the pages/api directory. The file name becomes the route path, and the default export is a request handler function.

Here's an example of a simple API route:

// pages/api/hello.js
export default function handler(req, res) {
  res.status(200).json({ message: 'Hello, World!' });
}
Enter fullscreen mode Exit fullscreen mode

In this example, the /api/hello route responds with a JSON object containing a "Hello, World!" message.

Handling HTTP Methods

API routes can handle different HTTP methods like GET, POST, PUT, DELETE, etc. You can use conditional statements based on the req.method to handle specific methods.

Here's an example:

// pages/api/users.js
export default function handler(req, res) {
  if (req.method === 'GET') {
    // Handle GET request
    res.status(200).json({ users: [] });
  } else if (req.method === 'POST') {
    // Handle POST request
    const newUser = req.body;
    // Process the new user data
    res.status(201).json({ message: 'User created' });
  } else {
    res.status(405).json({ message: 'Method not allowed' });
  }
}
Enter fullscreen mode Exit fullscreen mode

In this example, the /api/users route handles GET and POST requests differently. It returns a list of users for GET requests and creates a new user for POST requests.

Connecting to Databases

API routes can connect to databases or external services to perform data operations. You can use libraries like prisma, mongoose, or any other database client to interact with your database.

Here's an example using Prisma:

// pages/api/posts.js
import prisma from '../../lib/prisma';

export default async function handler(req, res) {
  if (req.method === 'GET') {
    const posts = await prisma.post.findMany();
    res.status(200).json({ posts });
  } else if (req.method === 'POST') {
    const { title, content } = req.body;
    const newPost = await prisma.post.create({
      data: {
        title,
        content,
      },
    });
    res.status(201).json({ post: newPost });
  } else {
    res.status(405).json({ message: 'Method not allowed' });
  }
}
Enter fullscreen mode Exit fullscreen mode

In this example, the /api/posts route uses Prisma to fetch posts from a database for GET requests and create a new post for POST requests.

Authentication and Authorization

API routes can handle authentication and authorization to protect sensitive data or restrict access to certain endpoints. You can use libraries like jsonwebtoken or next-auth to implement authentication strategies.

Here's an example using jsonwebtoken:

// pages/api/protected.js
import jwt from 'jsonwebtoken';

const secretKey = process.env.JWT_SECRET;

export default function handler(req, res) {
  const token = req.headers.authorization?.split(' ')[1];

  if (!token) {
    res.status(401).json({ message: 'Unauthorized' });
    return;
  }

  try {
    const decoded = jwt.verify(token, secretKey);
    // Access the authenticated user data from `decoded`
    res.status(200).json({ message: 'Protected data' });
  } catch (error) {
    res.status(401).json({ message: 'Invalid token' });
  }
}
Enter fullscreen mode Exit fullscreen mode

In this example, the /api/protected route checks for the presence of a JWT token in the Authorization header. It verifies the token using the secret key and grants access to protected data if the token is valid.

Let's go for Part 3 : Deployment and Advanced techniques !

Or go back to your fundamentals :

Top comments (0)