Next.js App Router の Route Handler で最新の API を作ろう

目次

Route Handler とは

こちらの記事をお読みになられた方であれば、Pages Router での API Routes についてはご存知かと思いますが、App Router では書き方が大きく変わります。

今回は、Next.js 13 以降で推奨されている App Router による Route Handler の書き方についてまとめておきたいと思います。

もしご自身で先に公式ドキュメントをご覧になられる方は下のリンクから眺めてみてください。
Route Handlers ー 公式ドキュメントです。

対象となる方

  • Next.js 13 以降を使っている方
  • Pages Router から App Router へ移行予定の方
  • API Routes の最新の書き方を学びたい方

 ※ このドキュメントは Next.js 16.x 系で書いていきます。

Pages Router での API Routes のおさらい

Pages Router を使ったことがある方であれば、pages/api/ ディレクトリ配下にファイルを作成して、エクスポートした関数でリクエストを処理していたかと思います。

// pages/api/posts.js
export default function handler(req, res) {
  if (req.method === 'GET') {
    res.status(200).json({ posts: [] })
  } else if (req.method === 'POST') {
    res.status(201).json({ message: 'Post created' })
  }
}

ですが App Router では、この書き方は使えません。Route Handler という新しいパターンで API を構築していく必要があります。

Route Handler の基本

App Router では、app/api/ ディレクトリ配下に route.ts (または route.js) という名前でファイルを作成します。

その中で、GETPOSTPUTDELETE といった HTTP メソッドに対応した関数をエクスポートします。

// app/api/posts/route.ts
export async function GET(request: Request) {
  return Response.json({ posts: [] })
}

export async function POST(request: Request) {
  return Response.json({ message: 'Post created' }, { status: 201 })
}

Pages Router との大きな違いは以下の通りです:

  1. ファイル構造: pages/api/[filename]app/api/[dir]/route.ts
  2. 関数の書き方: export default function handler()export async function GET/POST/...
  3. レスポンス形式: res.json()Response.json()

Pages Router ではリクエスト・レスポンスオブジェクトを受け取っていましたが、Route Handler では Request オブジェクトを受け取り、Response オブジェクトを返す形になります。

実装例:シンプルなブログ API

では、実際に Route Handler を実装してみましょう。今回は、ブログの記事一覧を取得・作成する API を作ります。

まずは環境を整えましょう。

npx create-next-app@latest blog-app --typescript
cd blog-app

次に、モデルファイルを作成します。lib/types.ts を以下の内容で作成してください。

// lib/types.ts
export interface Post {
  id: number;
  title: string;
  content: string;
  createdAt: string;
}

そして、ダミーデータを用意するファイルを作成します。lib/posts.ts を以下のようにしてください。

// lib/posts.ts
import { Post } from './types';

let posts: Post[] = [
  {
    id: 1,
    title: 'Next.js の基本',
    content: 'Next.js は React フレームワークです...',
    createdAt: new Date().toISOString()
  },
  {
    id: 2,
    title: 'Route Handler について',
    content: 'App Router での API 実装方法です...',
    createdAt: new Date().toISOString()
  }
];

export function getAllPosts(): Post[] {
  return posts;
}

export function addPost(title: string, content: string): Post {
  const newPost: Post = {
    id: posts.length + 1,
    title,
    content,
    createdAt: new Date().toISOString()
  };
  posts.push(newPost);
  return newPost;
}

export function getPostById(id: number): Post | undefined {
  return posts.find(post => post.id === id);
}

では、Route Handler を作成しましょう。app/api/posts/route.ts を以下の内容で作成してください。

// app/api/posts/route.ts
import { getAllPosts, addPost } from '@/lib/posts';
import { Request, NextResponse } from 'next/server';

export async function GET(request: Request) {
  try {
    const posts = getAllPosts();
    return NextResponse.json({
      success: true,
      count: posts.length,
      data: posts
    });
  } catch (error) {
    return NextResponse.json(
      { success: false, error: 'Failed to fetch posts' },
      { status: 500 }
    );
  }
}

export async function POST(request: Request) {
  try {
    const body = await request.json();
    const { title, content } = body;

    if (!title || !content) {
      return NextResponse.json(
        { success: false, error: 'Title and content are required' },
        { status: 400 }
      );
    }

    const newPost = addPost(title, content);
    return NextResponse.json({
      success: true,
      data: newPost
    }, { status: 201 });
  } catch (error) {
    return NextResponse.json(
      { success: false, error: 'Failed to create post' },
      { status: 500 }
    );
  }
}

次に、個別の記事を取得・更新するための動的ルートを作成します。app/api/posts/[id]/route.ts を以下のようにしてください。

// app/api/posts/[id]/route.ts
import { getPostById } from '@/lib/posts';
import { NextResponse } from 'next/server';

export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  try {
    const postId = parseInt(params.id);
    const post = getPostById(postId);

    if (!post) {
      return NextResponse.json(
        { success: false, error: 'Post not found' },
        { status: 404 }
      );
    }

    return NextResponse.json({
      success: true,
      data: post
    });
  } catch (error) {
    return NextResponse.json(
      { success: false, error: 'Failed to fetch post' },
      { status: 500 }
    );
  }
}

これで完成です! では動作を確認してみましょう。

npm run dev

サーバーが起動したら、以下の URL にアクセスしてみてください。

GET /api/posts にアクセスすると、以下のような JSON が返されます。

{
  "success": true,
  "count": 2,
  "data": [
    {
      "id": 1,
      "title": "Next.js の基本",
      "content": "Next.js は React フレームワークです...",
      "createdAt": "2026-08-30T00:00:00.000Z"
    },
    {
      "id": 2,
      "title": "Route Handler について",
      "content": "App Router での API 実装方法です...",
      "createdAt": "2026-08-30T00:00:00.000Z"
    }
  ]
}

POST /api/posts にリクエストを送ります。

curl -X POST http://localhost:3000/api/posts \
  -H "Content-Type: application/json" \
  -d '{"title": "新しい記事", "content": "これは新しい記事です"}'

すると、以下のようなレスポンスが返ります。

{
  "success": true,
  "data": {
    "id": 3,
    "title": "新しい記事",
    "content": "これは新しい記事です",
    "createdAt": "2026-08-30T00:00:00.000Z"
  }
}

GET /api/posts/1 にアクセスすると、特定の記事が取得できます。

{
  "success": true,
  "data": {
    "id": 1,
    "title": "Next.js の基本",
    "content": "Next.js は React フレームワークです...",
    "createdAt": "2026-08-30T00:00:00.000Z"
  }
}

Pages Router との比較

「結局 Pages Router の API Routes と何が違うの?」という方もいらっしゃるかと思いますので、Pages Router での実装と比較してみましょう。

Pages Router での実装

// pages/api/posts.js
export default function handler(req, res) {
  if (req.method === 'GET') {
    const posts = getAllPosts();
    res.status(200).json({
      success: true,
      count: posts.length,
      data: posts
    });
  } else if (req.method === 'POST') {
    const { title, content } = req.body;
    if (!title || !content) {
      res.status(400).json({ error: 'Title and content required' });
      return;
    }
    const newPost = addPost(title, content);
    res.status(201).json({ success: true, data: newPost });
  }
}

App Router での実装

// app/api/posts/route.ts
export async function GET(request: Request) {
  const posts = getAllPosts();
  return NextResponse.json({
    success: true,
    count: posts.length,
    data: posts
  });
}

export async function POST(request: Request) {
  const body = await request.json();
  const { title, content } = body;
  if (!title || !content) {
    return NextResponse.json(
      { error: 'Title and content required' },
      { status: 400 }
    );
  }
  const newPost = addPost(title, content);
  return NextResponse.json({ success: true, data: newPost }, { status: 201 });
}

ぱっと見た感じ、Pages Router でも App Router でも「そこまで大きな違いはないのでは?」と思うかもしれませんが、実は以下のような利点があります:

  1. TypeScript のサポートが充実RequestResponse オブジェクトが標準の Web API に基づいており、型安全性が高い
  2. HTTP メソッドの分離 – 複数の HTTP メソッドが一つのファイルに混在せず、関数として分離されている
  3. テストがしやすい – 各メソッドが独立した関数なので、ユニットテストを書きやすい
  4. 将来の Next.js 更新に対応しやすい – Pages Router は段階的に廃止されていく予定なので、App Router への移行は必須

まとめ

以上で Pages Router の API Routes と App Router の Route Handler の比較を終えたいと思います。

多くの新規プロジェクトでは App Router が標準になってきていますので、これから Next.js を学ぶ方は Route Handler の書き方を重点的に学ぶことをオススメします。

また、既存のプロジェクトが Pages Router を使っている場合でも、新しく追加する API は App Router で実装するというのも一つの手かもしれませんね。

API 設計をしっかり整えることができれば、フロントエンドとバックエンド(あるいは別チーム)との連携も見えてきそうですね ^^v ビバ!美しい API 設計!

ということで、ではまた〜

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

Web Developer / Educator