Serverless FrameworkとExpressでお手軽にAPIサーバを作ろう
この記事は、Recruit Engineers Advent Calendar 20176日目の記事です。
こんにちは。最近マルチロールな働き方をしていて自分が何者なのかわからなくなってきた加藤です。現在リクルートジョブズというところでエンジニアをしています。
日頃、社内用のちょっとしたツールとか、試験的なAPIとかを作るのにAWS LambdaやDynamoDBを使ってサーバレスな開発をしているのですが、この記事ではその際によくやるServerless FrameworkとExpressを活用した開発手法の紹介をしたいと思います。
Expressといえば、Node.jsのWebアプリケーションフレームワークとして有名ですが、最近ではaws-serverless-expressというのを使うと、AWS LambdaでもExpressを使って開発ができます。サラッと書きましたが、正直こいつが出てきた時僕は100万starくらいしたい気持ちでした。そのくらいlambdaを便利にしてくれたと思っています。
一方、Serverless Frameworkというと「?」となる方も多いかと思います。ざっくり説明すると、API GatewayやAWS Lambdaなどのフルマネージドなサービスを組み合わせる、 サーバレス・アーキテクチャ による開発を簡単にしてくれるツールです。AWSには標準でCloud Formationというオーケストレーション機能がありますが、イメージとしては、Cloud FormationにわかりやすいCUIと拡張性のある開発環境がくっついたような感じです。プラグインを使うとローカルにAPI GatewayやLambda、S3、DynamoDBなどの開発環境を立ててることができるようになっています。
これら二つを組み合わせて使うと、簡単にサーバレスなAPIの開発ができるというお話です。
つくるもの
要件
よくありそうなTODOアプリのAPIをつくります。
機能 | メソッド | パス |
---|---|---|
一覧 | GET | /items |
追加 | POST | /item |
取得 | GET | /item/:id |
更新 | PUT | /item/:id |
削除 | DELETE | /item/:id |
構成
API Gateway + Lambda + DynamoDBというベーシックな構成です。
ソースコード
前提条件
- AWS CLIの設定が完了していること
- node.jsがインストールされていてnpmコマンドが使えること
- https://serverless.com/に登録していること
手順
1. 準備
serverlessのセットアップ
まず、npmでserverless
をグローバルに入れます。
$ npm install -g serverless
これで、serverless
,slss
,sls
コマンドが使えるようになります。
$ sls
サブコマンドを指定しないで呼ぶと、ヘルプメニューとして使えるコマンド一覧が表示されます。この記事でもこの先何回もsls
コマンドを使っていくことになります。
このタイミングで、Serverless.comにログインしておきます。
$ sls login
ブラウザが開くはずなので、よしなにログイン処理をすすめてください。
プロジェクトの初期化
$ mkdir sls-todo && cd sls-todo $ npm init
必要に応じて、.gitignore
もよしなに書いてください。
さて、この手順の仕上げとして、今回必要になるファイルとディレクトリをあらかじめ設置しておきます。以下のような構造にしました。
. ├── package.json ├── seeds │ └── todo-items.json ├── serverless.yml └── src ├── app.js └── routes └── index.js
ここで、serverless.yml
というファイルを作成しました。これは、Serverless Frameworkの設定ファイルです。Serverless Frameworkはこのファイルに書かれている内容を実行していきますので、アプリケーションの本体といっても過言ではないかもしれません。3. serverless.ymlを書くに、完成形のserverless.ymlを載せてあります。
2. 使うもののインストール
Lambdaで使うやつ
$ npm install --save express aws-sdk aws-serverless-express body-parser cors compression
Serverlessプラグイン
- serverless
- serverless-offline
- serverless-dynamodb-local
- serverless-plugin-include-dependencies
$ npm install --save-dev serverless serverless-offline serverless-dynamodb-local serverless-plugin-include-dependencies
3. serverless.ymlを書く
serverless.ymlを書くのは、最初はちょっと骨が折れるかもしれません。ボトムアップに作っていくのは結構きついものがあるので、ほぼ全部入りのUser GuideのServerless.ymlから、自分が使いたい部分を抜き出してきて使うのがオススメです。各リソースのイベントについて細かい設定がしたい場合、Eventsの各ページが参考になります。
今回は以下のように設定を書きました。各項目の内容はコメントに書いてある通りです。
service: sls-todos # 実行環境に関する設定 provider: name: aws runtime: nodejs6.10 region: ap-northeast-1 # 利用するプラグインについて plugins: - serverless-offline - serverless-dynamodb-local - serverless-plugin-include-dependencies # デプロイ時のパッケージの内容について package: include: - src/** # lambdaを実行するトリガの設定。今回はAPI Gatewayのルーティング設定 functions: sls-todo: role: ${DYNAMO_ACCESS_ROLE} # dynamoにアクセスできるロール description: TODOリストAPIのルーティング handler: src/routes/index.handler events: - http: path: /todos method: get cors: true - http: path: /todo method: post cors: true - http: path: /todo/{id} method: get cors: true request: parameters: paths: id: true - http: path: /todo/{id} method: put cors: true request: parameters: paths: id: true - http: path: /todo/{id} method: delete cors: true request: parameters: paths: id: true # ローカルのdynamodbの起動設定 custom: dynamodb: start: port: 8888 inMemory: true migrate: true seed: true seed: test: sources: - table: sls-todos sources: [./seeds/todo-items.json] # CloudFormationの形式でのリソース定義 resources: Resources: slsTodos: Type: AWS::DynamoDB::Table Properties: TableName: sls-todos AttributeDefinitions: - AttributeName: id AttributeType: S KeySchema: - AttributeName: id KeyType: HASH ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1
4. DynamoDBのローカルへのインストールと起動
serverless.yml
のplugins
を書いたことで、プラグインが提供するコマンドが使えるようになっています。
sls
と打つと、以下のようなコマンドが一覧に追加されて表示されるはずです。
dynamodb ...................... undefined dynamodb migrate .............. Creates local DynamoDB tables from the current Serverless configuration dynamodb seed ................. Seeds local DynamoDB tables with data dynamodb start ................ Starts local DynamoDB dynamodb remove ............... Removes local DynamoDB dynamodb install .............. Installs local DynamoDB
$ sls dynamodb install
これで、ローカルにDynamoDBがインストールされます。 DBの準備ができたので、今度はseedファイルの準備をします。
seeds/todo-items.json
[ { "id" : "test", "todo" : "Hello World!" } ]
seedファイルの準備ができたら、dynamodbを起動します。
$ sls dynamodb start
先ほど、serverless.ymlのdynamodb
パートでの起動時オプションにseed: true
とmigrate: true
をつけたので、起動と同時にseedファイルの内容がDBに反映されます。
5. ExpressでAPIを書く
使っているミドルウェアは少し違いますが、基本的には普通にExpressを利用するときと同じ書き方ができます。ここでは、ルーティングに関わらない共通の処理をapp.js
にまとめています。
src/app.js
const express = require('express'); const bodyParser = require('body-parser'); const cors = require('cors'); const compression = require('compression'); const awsServerlessExpressMiddleware = require('aws-serverless-express/middleware'); const app = express(); app.set('view engine', 'pug'); app.use(compression()); app.use(cors()); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(awsServerlessExpressMiddleware.eventContext()); module.exports = app;
一つ完全に違うのはapp.js
がroutes
以下のルーターを読むのではなく、ルーターがapp.js
を読み込んでいるという点です。これは、app.js
を核にして一つのサービスが立つのではなく、各ルーターが独立したサービスとして立ちapp.js
という共通設定を利用する形になっているためです。
src/routes/index.js
const express = require("express"); const awsServerlessExpress = require('aws-serverless-express'); const AWS = require("aws-sdk"); const app = require("../app"); const router = express.Router(); //ローカルかリモートかで、dynamodbのconfigを切り替える const dynamoConfig = process.env.NODE_ENV === "development" ? { region: "localhost", endpoint: "http://localhost:8888", } : {}; const ddc = new AWS.DynamoDB.DocumentClient(dynamoConfig); //一覧 router.get('/todos', (req, res, next) => { ddc.scan({ TableName: "sls-todos" }).promise() .then((result) => res.send(result)) .catch(next); }); //追加 router.post('/todo', (req, res, next) => { ddc.put({ TableName: "sls-todos", Item: req.body.Item }).promise() .then((result) => res.send(result)) .catch(next); }); //取得 router.get('/todo/:id', (req, res, next) => { ddc.get({ TableName: "sls-todos", Key:{ id: req.params.id, } }).promise() .then((result) => res.send(result)) .catch(next); }); //更新 router.put('/todo/:id', (req, res, next) => { req.body.Item.id = req.params.id; ddc.get({ TableName: "sls-todos", Item: req.body.Item }).promise() .then((result) => res.send({ message: "update success!" })) .catch(next); }); //削除 router.delete('/todo/:id', (req, res, next) => { ddc.delete({ TableName: "sls-todos", Key:{ id: req.params.id, } }).promise() .then((result) => res.send({ message: "delete success!" })) .catch(next); }); router.use((err, req, res, next) => { res.status(500).send(err); }); app.use("/", router); const server = awsServerlessExpress.createServer(app); exports.handler = (event, context) => awsServerlessExpress.proxy(server, event, context);
6. ローカルで動かす
ここまでで、アプリケーションの設定とコードの準備が全て整いました。offline
コマンドで、ローカルにサーバを立ち上げてみましょう。
$ sls offline
Serverless: Starting Offline: dev/ap-northeast-1. Serverless: Routes for sls-todo: Serverless: GET /todos Serverless: POST /todo Serverless: GET /todo/{id} Serverless: PUT /todo/{id} Serverless: DELETE /todo/{id} Serverless: Offline listening on http://localhost:3000
localhost:3000
でサーバが立ち上がります。serverless.ymlに設定したルーティングに対して、リクエストを投げてみましょう。curlやブラウザでやるのもいいですが、個人的にはpostmanがオススメです。
7. デプロイする
$ sls deploy
これだけです。ローカルの時と同じく、デプロイされた先のAPIを叩いてみましょう。
$ sls remove
挙動の確認ができて、必要がなくなったら畳んでしまいましょう。リモートにデプロイしたまま放置していると、知らないうちにお金がかかっていることがあるので...。
まとめ
- Serverless frameworkを使うと嬉しいことがたくさん
それではみなさま、よきサーバレスライフを!!