Next.jsのAPI Router使えば簡単なAPIができます!
Next.js 9よりAPI Routerの機能が実装されました。それを使えば、これまでExpress.jsやその他サーバーを別に立ててAPIサーバーとしていたのが、Next.js一本で完結することができます。
そこで、MongoDBを使ってデータを出力するAPIを構築してみようと思います。
この記事はUse middleware in Next.js without custom server | Hoang Voという非常にわかりやすい記事があり、つまづいた部分を含め自分の勉強の意味で解釈を加えながらその技術を紹介できればと思い書いています。
いろいろ実装が甘かったり、勘違いもあるとは思いますが、、、
環境
Next.js 9.3.4
まずは、簡単なAPIの実験から
最初に、簡単なJSONを返すだけのAPIを実装してみます。これは公式のチュートリアル にもありますが・・・
ここではyarnを使っています。まずはとりあえずdev serverを起動します。
$ yarn create next-app testapp
$ cd testapp
$ yarn dev
次に、pages/api/message.js
を作成し、ただのメッセージを返すだけのapiを作成してみます。
export default (req, res) => {
res.status(200).json({
message: "Hello, API!",
});
};
apiをたたいてみましょう。
$ curl http://localhost:3000/api/message
{"message":"Hello, API!"}
このように、pagesフォルダの中にapiフォルダを作成(このapiのフォルダ名は固定で変更できません)したら、その中に(req,res)を受け取る関数を書いてexportすれば、それだけでapiとして機能します。簡単。
mongoDBに接続してみる
次に、mongoDBに接続してみます。ここでは、すでにmongoDBサーバーが起動し、test
DBの中にいくつかのusers
コレクションが既にあるとします。
まず、mongoDBを参照するためにpackageを追加します
$ yarn add --dev mongodb
次に、ユーザー一覧を出力するusers.js
エントリポイントを追加します。
import { MongoClient } from "mongodb";
const uri = "<<各自のmongoDB接続文字列>>";
const handler = (req, res) => {
const client = new MongoClient(uri);
client.connect(async (err) => {
const collection = client.db("test").collection("users");
const users = await collection.find().toArray();
res.json(users);
});
};
export default handler;
試してみます。
$curl http://localhost:3000/api/users
[{"_id":"5e8fe5fcf5577c443c17b3d3","name":"testA","password":"testApass","age":36},
"_id":"5e8fe5fcf5577c443c17b3d4","name":"testB","password":"testBpass","age":24},
"_id":"5e8fe5fcf5577c443c17b3d5","name":"testC","password":"testCpass","age":38}]
↑本来は取得結果は一列に表示されていますが、改行を入れています。
無事、mongoDBからデータ取得ができました。
ですが、もしapiがuser一覧取得だけでなく、他のコレクション取得用に複数のエントリポイントを作る場合、それぞれに接続の手続きを書くのはメンドくさいですね。
そこで、次はDB接続はミドルウェアにして、reqにDBアクセス用のオブジェクトを追加してみます。
DBを利用するAPIは、そのミドルチェアを利用すればコネクションが確立した状態で、DB操作に専念することができそうです。
DB接続をミドルウェア化する
ところで、ミドルウェアについては、これがExpressであれば
app.use((req,res,next)=>{
})
app.use(cookieParser())
のような形でできますが、Next.jsには残念ながらそのようなことはできないので、ラッパー関数で対応することにします。
まず、ルートにmeddlewares
ディレクトリを作り、そこにdatabase.js
ミドルウェアを作ってみます。
import { MongoClient } from "mongodb";
const uri =
"<<各自のmongoDB接続文字列>>";
const withDatabase = (handler, collectionName) => {
return async (req, res) => {
const client = new MongoClient(uri);
await client.connect();
req.db = client.db("test").collection(collectionName);
return handler(req, res);
};
};
export default withDatabase;
ほぼ、先程と似たような構造です。
違いは、最終的にapiとして動作させたいので、(req,res)を受け取る関数を返す関数にしていること、そして、aptの中のhandlerが受け取るreqにdbオブジェクトを付与して、そのreqを引数にしてhandlerを適用していること、になります。
ここの構造は次のようなイメージです。
このwithDatabaseミドルウェアを利用すると、users.js
は次のようになります。
import withDatabase from "../../middlewares/database";
const handler = async (req, res) => {
// withDatabaseによってreqにはdbが付与されている
const users = await req.db.find().toArray();
res.json(users);
};
export default withDatabase(handler, "users");
最後のexportで、handlerをwithDatabaseでくるんでいるのがポイントになります。
だいぶスッキリしました。これで、今後さまざまなapiを作成しても、このミドルウェアさえimportすればdbアクセスできそうです。
next-connectで、より自然な表現へ
ですが、もし複数のミドルウェアを利用したい場合、
export default middleA(middleB(middleC(handler)))
のように、非常にややこしいことになりそうです。
やはり、Expressのように
app.use(middleA())
app.use(middleB())
app.use(middleC())
と書けると嬉しいです。
そこで、next-connect - npmを利用します。
これはNext.jsでExpressのようなミドルウェアの表現ができるようになる素敵なPackageです。
まず、next-connectをインストールします。
$ yarn add --dev next-connect
そして、次にdatabase.jsミドルウェアの内容を変更します。
import { MongoClient } from "mongodb";
const uri = "<<自分のmongoDB接続文字列>>";
const withDatabase = (collectionName) => {
//ここの引数にnextを追加
return async (req, res, next) => {
const client = new MongoClient(uri);
await client.connect();
req.db = client.db("test").collection(collectionName);
next(); // return handler(req,res)から変更
};
};
export default withDatabase;
next()で終了しているところなどは、まるでExpressの普通のミドルウェアのようです。
次に、users.jsも編集します。
import nextConnect from "next-connect"; //追加
import withDatabase from "../../middlewares/database";
const handler = nextConnect();
// useでミドルウェアを指定
handler.use(withDatabase("users"));
// このreqには、dbオブジェクトが既に付与されています
handler.get(async (req, res) => {
const users = await req.db.find().toArray();
res.json(users);
});
export default handler; // すっきりしました
これにより、export default がすっきりしました。また、表記法はExpressのミドルウェアの利用法とほぼ同等になりました。複数のミドルウェアを使用したい場合も、handler.use(....)
を重ねていくことで利用可能になります。
ここで、handler.get
とあるとおり、handler.post
でクライアントサイドからのPOSTリクエストに応えるapiを作成することもできます。
最後に
これで、Next.jsのみで外部サーバーをたてずに簡単なDBアクセス可能なAPIを作成することができました。
自前で完結することで、開発が容易になる側面もあるのではないでしょうか?
次は、Use middleware in Next.js without custom server | Hoang Voのブログのシリーズもありますが、session、passportを使った認証も書きたいと思います。