RESTful API を作成する!!(with Next.js & MongoDB & TypeScript)
はじめに
Next.js
の勢いが止まらない今、Next.js
第一弾として NoSQLのMongoDB
と連携し、簡単なAPIを作成する手順をまとめました。
認識が甘い点など各所で見受けられると思います。そのような点がございましたら、ご教授いただけると幸いです
Next.js & MongoDB (TypeScript化) 簡易セットアップ
Next.js
公式の github
上にはたくさんの examples が掲載されています。
今回はこの中の with-typescript
フォルダーをベースとします。
セットアップは非常に簡単で、ターミナルに移動し、以下のコマンドを叩くだけです。
yarn create next-app --example with-typescript プロジェクト名
ファイル構成を確認します。
src フォルダーを作成し、components interfaces pages utils フォルダーを src 直下に配置します。
続いてMongoDB
とaxios
をインストールします。
yarn add mongodb axios
yarn add -D types/mongodb
本記事の目標
utils > sample-data から取得しているデータを、MongoDB
を通して作り出したデータに置き換えることを再現したいと思います。
コードを書く
①environment variables をセットアップ
ルートディレクトリに .env.local
ファイルを作成します。
MONGODB_URI= // Your MongoDB connection string
MONGODB_DB= // dbname
②mongodb.ts ファイル作成
utils フォルダー内、sample-data.ts ファイルを mongodb.ts にリネームし、以下のようにコードを書き換えます。
import { MongoClient, Db } from 'mongodb';
const { MONGODB_URI: uri, MONGODB_DB: dbName } = process.env;
let cachedClient: MongoClient;
let cachedDb: Db;
if (!uri) {
throw new Error(
'Please define the MONGODB_URI environment variable inside .env.local'
);
}
if (!dbName) {
throw new Error(
'Please define the MONGODB_DB environment variable inside .env.local'
);
}
export async function connectToDatabase() {
if (cachedClient && cachedDb) { //キャッシュ変数が入力されているか確認
return { client: cachedClient, db: cachedDb };
}
const client = await MongoClient.connect(uri!, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
const db = await client.db(dbName);
cachedClient = client;
cachedDb = db;
return { client, db };
}
③ MongoDB でデータを作成
MongoDB
内、Clusters > COLLECTIONS でデータベースを作成します。
次に INSERT DOCUMENT ボタンを押して、データを作成します。
複数個、データを作成しておきます。
エディタに移動し、取得するデータの型付けをします。(User に変更を加えます。)
export type User = {
- id: number
+ _id:string
+ id:string
name: string
}
④MongoDB との接続
MongoDB との接続を役割として持つ src > pages > api > users > index.ts に移動しコードを以下のように変更します。
import { NextApiRequest, NextApiResponse } from "next";
import { connectToDatabase } from "../../../utils/mongodb";
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
try {
const { method } = req;
switch (method) {
case "GET":
// Get data from mongodb
const { db } = await connectToDatabase();
const data = await db.collection("user").find().toArray(); //連想配列化
res.status(200).json(data); // json 形式でデータを取得
break;
default:
res.setHeader("Allow", ["GET", "PUT"]);
res.status(405).end(`Method ${method} Not Allowed`);
}
} catch (err) {
res.status(500).json({ statusCode: 500, message: err.message });
}
};
export default handler;
上述のコードは、next.js > examples > api-routes-rest >pages >api >user/[id].js を参考にしました。↓
/api/user にアクセスするとデータが取得できていることが確認できます。
各コンポーネントにも変更を加えていきます。
users/index.ts
-import { GetStaticProps } from 'next'
+import { GetServerSideProps } from "next";
import Link from 'next/link'
import { User } from '../../interfaces'
import { sampleUserData } from '../../utils/sample-data'
import Layout from '../../components/Layout'
import List from '../../components/List'
type Props = {
items: User[]
}
const WithStaticProps = ({ items }: Props) => (
<Layout title="Users List | Next.js + TypeScript Example">
<h1>Users List</h1>
<p>
Example fetching data from inside <code>getStaticProps()</code>.
</p>
<p>You are currently on: /users</p>
<List items={items} />
<p>
<Link href="/">
<a>Go home</a>
</Link>
</p>
</Layout>
)
-export const getStaticProps: GetStaticProps = async () => {
- // Example for including static props in a Next.js function component --page.
- // Don't forget to include the respective types for any props passed -into
- // the component.
- const items: User[] = sampleUserData
- return { props: { items } }
-}
+export const getServerSideProps: GetServerSideProps = async () => {
+ const response = await axios.get<User[]>+("http://localhost:3000/api/users");
+ const items = await response.data;
+
+ return { props: { items } };
+};
export default WithStaticProps
SSG(getStaticProps
関数を使用すると)ではビルド時のみのデータ取得を行うため、頻繁に変化のあるデータには向きません。
そこで SSR(getServerSideProps
関数を使用)化することで、ビルド時に実行され、またリクエストごとにデータ取得を行います。
getServerSideProps
の関数内部では、外部データを取得して、取得したデータを props としてページに渡すことができます。
コードでも確認できるように、items prop は WithStaticProps コンポーネントに渡されます。
users/[id].tsx
import { GetStaticProps, GetStaticPaths } from "next";
import { User } from "../../interfaces";
import Layout from "../../components/Layout";
import ListDetail from "../../components/ListDetail";
import axios from "axios";
type Props = {
item?: User;
errors?: string;
};
const StaticPropsDetail = ({ item, errors }: Props) => {
if (errors) {
return (
<Layout title='Error | Next.js + TypeScript Example'>
<p>
<span style={{ color: "red" }}>Error:</span> {errors}
</p>
</Layout>
);
}
return (
<Layout
title={`${
item ? item.name : "User Detail"
} | Next.js + TypeScript Example`}
>
{item && <ListDetail item={item} />}
</Layout>
);
};
export default StaticPropsDetail;
export const getStaticPaths: GetStaticPaths = async () => {
// Get the paths we want to pre-render based on users
const response = await axios.get<User[]>("http://localhost:3000/api/users");
const items = await response.data;
const paths = items.map((user) => ({
params: { id: user.id },
}));
// We'll pre-render only these paths at build time.
// { fallback: false } means other routes should 404.
return { paths, fallback: false };
};
// This function gets called at build time on server-side.
// It won't be called on client-side, so you can even do
// direct database queries.
export const getStaticProps: GetStaticProps = async ({ params }) => {
try {
const id = params?.id;
const response = await axios.get<User[]>("http://localhost:3000/api/users");
const items = await response.data;
const item = items.find((data) => data.id === id);
// By returning { props: item }, the StaticPropsDetail component
// will receive `item` as a prop at build time
return { props: { item } };
} catch (err) {
return { props: { errors: err.message } };
}
};
角括弧で名前を持つファイルは、Next.js
では動的なページになります。
そして、getStaticPaths
という非同期関数を使って、動的ルーティングによりページを静的に生成することを可能にします。この関数の中では、id として とりうる値 のリストを返さなければなりません。
最後に getStaticProps
を実装します。今回のケースでは受け取った id に基づいて必要なデータを取得します。getStaticProps
は params を受け取りますが、そこには id が含まれています。
以上の変更により、正常にデータが反映されています。
ユーザ情報に URL が対応付いており、REST
な API を作成できました!
以上になります。ここまで読んでいただきありがとうございました!!
Discussion