【Next.js】GCPのデータベースをNext.jsプロジェクトに組み込みたい
こちらではGCP(Google Cloud Platform)をNext.jsのプロジェクトに組み込む方法を解説していきます。
- Next.jsプロジェクトの立ち上げ
- GCPでデータベースの作成
- GCPシェルからデータベースの作成
- データベース、テーブルの作成
- Next.jsプロジェクトにMySQLを接続する準備
- データベースへの接続
- データベース接続用のファイルを作成する
- APIの作成
- 取得したデータの表示
Next.jsプロジェクトの立ち上げ
まずはNext.jsのプロジェクトを立ち上げましょう。
mkdir myproject cd myproject npm init
GCPでデータベースの作成
GCPへアクセスし、[コンソールへ移動]のボタンを押しましょう。
次に
と進み、インスタンスの詳細設定を行なっていきます。
インスタンス名やパスワードは特に決まりがありません。
またリージョンに関しては、東京の場合、「asia-southeast1」となります。
ゾーンは任意で構いません。
GCPシェルからデータベースの作成
次にGCPの画面上部にあるシェルボタンから、シェルプロンプトを起動します。
一番左にあるアイコンですね。
シェルのインストールが終わると起動できるようになるので、起動しましょう。
シェルに以下のようなメッセージが表示されるので、
Welcome to Cloud Shell! Type "help" to get started. Your Cloud Platform project in this session is set to {YOUR_GCP_PROJECT}. Use “gcloud config set project [PROJECT_ID]” to change to a different project.
以下のようにコマンドを打ちます。
gcloud sql connect インスタンス名 --user=root
インスタンス名の部分には、先ほど作成したインスタンスの名前を入れましょう。
次にパスワードが聞かれるので、こちらも先ほど設定したパスワードを入れましょう。
すると以下のようなメッセージが表示され、MySQLの操作が可能となります。
Whitelisting your IP for incoming connection for 5 minutes...done. Connecting to database with SQL user [root].Enter password: Welcome to the MariaDB monitor. Commands end with ; or \g. Your MySQL connection id is 57 Server version: 5.7.14-google-log (Google)
データベース、テーブルの作成
次にデータベース、テーブルを作成していきます。
これまでMySQLを使用されてきた方は、同様の方法で作成することが可能です。
create database myproject use myproject create table posts (id INT NOT NULL AUTO_INCREMENT, title VARCHAR(255), body VARCHAR(255), PRIMARY KEY(id));
これで以下のようなテーブルが作成されました。
試しにレコードの挿入をしましょう。
insert into posts (title, body) values ("first title", "first body"); insert into posts (title, body) values ("second title", "second body"); select * from posts;
以下のように表示されていればレコードの挿入が成功しています。
Next.jsプロジェクトにMySQLを接続する準備
まずは必要なパッケージをプロジェクト直下でインストールしていきましょう。
(ここからはGCPのシェルプロンプトではなく、Next.jsのプロジェクトをインストールしたターミナルにコマンドを打ち込んでいきます。)
npm i serverless-mysql sql-template-strings
package.jsonにもbuildコマンドを追記します。(すでにscriptsに書き込まれているコマンドは消さないでください。)
"scripts": { "build": "next build" },
Serverless MySQL
GCPのようなサーバーレスMySQLへの接続を管理するためのパッケージ。このパッケージを導入することでデータベースに接続できるようになります。
またスケーリング可能なため、接続数が増えても自動的に拡大してくれる柔軟性があります。
GitHub - jeremydaly/serverless-mysql: A module for managing MySQL connections at SERVERLESS scale
SQL Template Strings
SQLのクエリを簡潔に書くことができるようになるパッケージ。mysql、mysql2、postgres、sequelizeで動作。
以下のような違いが出てくるようです。
const SQL = require('sql-template-strings') const book = 'harry potter' const author = 'J. K. Rowling' // SQL Template Stringsを導入しなかった場合 mysql.query('SELECT author FROM books WHERE name = ? AND author = ?', [book, author]) // SQL Template Stringsを導入した場合 mysql.query(SQL`SELECT author FROM books WHERE name = ${book} AND author = ${author}`)
またZEIT公式にも以下のように書かれている通り、SQLインジェクションの対策にも繋がるようです。
パラメータ化されたクエリを使用してSQLインジェクションによる攻撃を防ぐために、sql-template-stringsを使用することを強くお勧めします。
データベースへの接続
まずはnowコマンドが使えるようにインストールをしましょう。
npm i -g now
次にデータベースの情報をnowコマンドを使って登録していきましょう。
以下のフォーマットを使用して該当箇所にデータベースの情報を記入していきます。
now secrets add MYSQL_HOST $database-hostname && now secrets add MYSQL_USER $database-username && now secrets add MYSQL_DATABASE $database-name && now secrets add MYSQL_PASSWORD $database-password
私の場合であれば、以下のようになります。
now secrets add MYSQL_HOST "%" && now secrets add MYSQL_USER "root" && now secrets add MYSQL_DATABASE "myproject" && now secrets add MYSQL_PASSWORD "xxxxxxxx"
MYSQL_HOSTとMYSQL_USERに関してはGCPで作成したインスタンスの詳細画面からユーザータブにアクセスすると確認可能です。
データベースの情報に登録が成功すると、ターミナルに以下のようなメッセージが表示されます。
> Success! Secret mysql_host added (hiramori) [569ms] > Success! Secret mysql_user added (hiramori) [564ms] > Success! Secret mysql_database added (hiramori) [600ms] > Success! Secret mysql_password added (hiramori) [695ms]
これでデータベースの情報が登録され、接続ができるようになったはずです。
データベース接続用のファイルを作成する
先ほど登録した情報をもとに、データベース接続用関数を書き込んでおくためのファイルを作成しましょう。
プロジェクト直下で以下のコマンドを入力します。
mkdir lib cd lib touch db.js
lib/db.js
const mysql = require('serverless-mysql') const db = mysql({ config: { host: process.env.MYSQL_HOST, database: process.env.MYSQL_DATABASE, user: process.env.MYSQL_USER, password: process.env.MYSQL_PASSWORD } }) exports.query = async query => { try { const results = await db.query(query) await db.end() return results } catch (error) { return { error } } }
db.jsファイルは次の機能を実行します。
定義されたデータベースの情報を使用して、MySQLデータベースへの接続を作成します。クエリが解決されると接続が閉じられるようにする関数をエクスポートします。
最も重要な行は、await db.end()です。これにより、アプリが利用可能なすべての接続を使い果たすことを防ぎます。
これで、サーバーレス環境に最適な再利用可能なデータベース接続ができました。
APIの作成
次にAPIを作成していきましょう。
まずはpostsテーブルの全レコード取得を行うためのAPI作成です。
mkdir api && mkdir api/posts
cd api/posts
touch index.js
api/posts/index.js
const db = require('../../lib/db') const escape = require('sql-template-strings') module.exports = async (req, res) => { let page = parseInt(req.query.page) || 1 const limit = parseInt(req.query.limit) || 9 if (page < 1) page = 1 const posts = await db.query(escape` SELECT * FROM posts ORDER BY id LIMIT ${(page - 1) * limit}, ${limit} `) const count = await db.query(escape` SELECT COUNT(*) AS postsCount FROM posts `) const { postsCount } = count[0] const pageCount = Math.ceil(postsCount / limit) res.status(200).json({ posts, pageCount, page }) }
index.jsファイルは次の機能を実行します。
- 要求されたクエリパラメータを解析します
- クエリパラメータを使用して、必要なpostを決定します
- データベースから必要なpostのみを要求します
- データベースを照会して合計レコードを取得します
- レコード数を使用してページネーションを計算します
- 取得したpostとページネーションの詳細を応答として送信します
これが、サーバーレス環境でページネーションを正常に使用するために必要なすべてのAPIコードです。
次にpostsテーブルから指定したidのレコードのみを取得するAPIを作成します。
api/postsディレクトリ直下に新たなファイルを作成します。
touch post.js
api/posts/post.js
const db = require('../../lib/db') const escape = require('sql-template-strings') module.exports = async (req, res) => { const [post] = await db.query(escape` SELECT * FROM posts WHERE id = ${req.query.id} `) res.status(200).json({ profile }) }
post.jsファイルは次の機能を実行します。
- リクエストクエリパラメータを解析します
- クエリパラメータを使用して、データベースから単一のプロファイルを選択します
- 取得したプロファイルを応答として送信します。
これで、ルートに応じて、すべてのプロファイルまたは単一のプロファイルのいずれかを提供するAPIが作成されました。
取得したデータの表示
次に、それらを表示するアプリインターフェイスを作成する必要があります。
まずは必要なモジュールを取得しましょう。
npm i isomorphic-unfetch next react react-dom mkdir pages