Express.jsを利用してミニマムなローカルサーバを構築するサンプル

## はじめに

Web系エンジニアが検証やハッカソンでミニマムなCRUDのAPIを用意する際に便利なExpress.jsについてまとめてみることにしました。

## 前提

- データストアとしてはローカルファイル(.json)を利用します
- body-parserを利用します
- node, npmが利用できること
- Expressの説明はしません。
    - こちら参照：https://expressjs.com/
- curlでHTTPリクエストを検証します

## まとめ

先にサンプルコード全体を記載します。

```jsx
const express = require('express');
const bodyParser = require('body-parser');
const fs = require('fs')

const app = express();
app.use(bodyParser.json());

const fileName = 'todos.json';

// GET /todos
app.get('/todos', (req, res) => {
    fs.readFile('todos.json', 'utf8', (err, data) => {
        if (err) throw err;
        res.send(JSON.parse(data));
    });
});

// POST /todos
app.post('/todos', (req, res) => {
  const newTodo = req.body;
  fs.readFile('todos.json', 'utf8', (err, data) => {
    if (err) throw err;
    const todos = JSON.parse(data);
    todos.push(newTodo);
    fs.writeFile('todos.json', JSON.stringify(todos), (err) => {
        if (err) throw err;
        res.status(201).send(newTodo);
    });
  });
});

// PUT /todos/:id
app.put('/todos/:id', (req,res) => {
  const id = parseInt(req.params.id);
  const updatedTodo = req.body;
  fs.readFile('todos.json', 'utf8', (err,data) => {
    if (err) throw err;
    const todos = JSON.parse(data);
    const index = todos.findIndex(todo => todo.id === id);
    todos[index] = updatedTodo;
    fs.writeFile('todos.json', JSON.stringify(todos),(err) => {
      if(err)throw err;
      res.send(updatedTodo);
    });
  });
});

// DELETE /todos/:id
app.delete('/todos/:id',(req,res) => {
  const id = parseInt(req.params.id);
  fs.readFile('todos.json','utf8',(err,data) => {
    if(err) throw err;
    let todos = JSON.parse(data);
    todos = todos.filter(todo => todo.id !== id);
    fs.writeFile('todos.json', JSON.stringify(todos),(err) => {
      if(err) throw err;
      res.status(204).send();
    });
  });
});

// Server Configuration
const port = 3000;
app.listen(port, ()=> {
  console.log(`Server is running at http://localhost:${port}`);
})
```

## やってみたこと

### 必要なライブラリのインストール

まずはexpressを利用するのでそれを入れます。

また、参照系以外のAPIではローカルファイルにjson形式でデータを読み書きしたいので、パーサとして利用するbody-parserを入れておきます。ファイルアクセスにfsを利用しますが、ビルトインのライブラリのためインストールはしません。

```jsx
$ npm init -y
$ npm install express --save
$ npm install body-parser --save
```

### app.jsの作成 1/n

簡単なToDoアプリケーションでの利用を想定して、CRUDのAPIを用意します。

まずはGETリクエストが通るところまで書いていきます。

```jsx
const express = require('express');
const bodyParser = require('body-parser');
const fs = require('fs')

const app = express();
app.use(bodyParser.json());

// GET /todos
app.get('/todos', (req, res) => {
  fs.readFile('todos.json', 'utf8', (err, data) => {
    if (err) throw err;
    res.send(JSON.parse(data));
  })
})

// POST /todos

// PUT /todos:id

// DELETE /todos:id

// Server Configuration
const port = 3000;
app.listen(port, ()=> {
  console.log(`Server is running at http://localhost:${port}`);
})
```

その後、データ永続化用のjsonファイルを空で作成した上で起動します。

```jsx
$ echo "[]" > todos.json
$ node app.js
```

コマンド実行後、curlでGETアクセスして空配列が帰ることを確認します

```jsx
$ curl -X GET http://localhost:3000/todos
[]$
```

### app.jsの作成 2/n

次に残りのPOST, PUT, DELETEも埋めていきます。

- POST
    
    ```jsx
    // POST /todos
    app.post('/todos', (req, res) => {
      const newTodo = req.body;
      fs.readFile('todos.json', 'utf8', (err, data) => {
        if (err) throw err;
        const todos = JSON.parse(data);
        todos.push(newTodo);
        fs.writeFile('todos.json', JSON.stringify(todos), (err) => {
            if (err) throw err;
            res.status(201).send(newTodo);
        });
      });
    });
    ```
    
- PUT
    
    ```jsx
    // PUT /todos/:id
    app.put('/todos/:id', (req,res) => {
      const id = parseInt(req.params.id);
      const updatedTodo = req.body;
      fs.readFile('todos.json', 'utf8', (err,data) => {
        if (err) throw err;
        const todos = JSON.parse(data);
        const index = todos.findIndex(todo => todo.id === id);
        todos[index] = updatedTodo;
        fs.writeFile('todos.json', JSON.stringify(todos),(err) => {
          if(err)throw err;
          res.send(updatedTodo);
        });
      });
    });
    ```
    
- DELETE
    
    ```jsx
    // DELETE /todos/:id
    app.delete('/todos/:id',(req,res) => {
      const id = parseInt(req.params.id);
      fs.readFile('todos.json','utf8',(err,data) => {
        if(err) throw err;
        let todos = JSON.parse(data);
        todos = todos.filter(todo => todo.id !== id);
        fs.writeFile('todos.json', JSON.stringify(todos),(err) => {
          if(err) throw err;
          res.status(204).send();
        });
      });
    });
    ```
    

### テスト

curlコマンドを利用してデータの新規作成・変更・削除・参照ができるか確認します。

以下のコマンドを上から実行して、最終的に空配列が返る状態になっていれば期待動作しています。

```jsx
curl -X POST -H "Content-Type: application/json" -d '{"id": 1, "title":"test"}' http://localhost:3000/todos
curl -X GET http://localhost:3000/todos
curl -X PUT -H "Content-Type: application/json" -d '{"id": 1, "title":"updated test"}' http://localhost:3000/todos/1
curl -X DELETE http://localhost:3000/todos/1
curl -X GET http://localhost:3000/todos
```

## 最後に

JavaScriptでライトなAPIサーバをローカルに立てる際にExpressをよく使いますが、毎回同じような内容を書いているのと、ハッカソンで意外と分担に困って突然APIを立てる役になったけど慣れてないので焦って時間取られる、ということがたまにありましたので、すぐに取り出してコピペできるように備忘録も兼ねて記事化してみました。

急いでいるだれかの参考になれば幸いです。

## 参考

- Express - Node.js Web アプリケーション・フレームワーク https://expressjs.com/ja/