フリーランスの暇な時に書く技術メモ
Genki Tech

Next.js + typescriptでサーバーサイドのバックグラウンド処理(メインループ)の実装

最近ラズパイをいじっていて、Web画面をNext.jsで実装しつつ、センサーやモーターを制御するサーバーのバックグラウンド処理を実装したくなりました。案外まとまった情報が無かったので載せてみます。(ラズパイには触れません)

server.ts と周辺の実装もtypescriptで行いつつ、ちゃんとホットリロードするようにします。

サーバーサイドのメインループを実装

Next.jsのカスタムサーバー機能を使えば、Webサービス立ち上げ後にメインループを実装します。必要な機能をインストールします。

npm i -S express
npm i -D @types/express ts-node nodemon

ts-node は typescript を自前でビルドするのに必要で、nodemonは実行中にファイル更新がかかった場合リスタートする機能を持っています。

まとまってるts-node-devと言う便利そうなものがあるようですがNext.jsと相性が悪いので今回は不採用。

server/index.ts を作成して以下の様に記述します。

import express, { Request, Response } from "express";
import next from "next";
import { Main } from "./main";

const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();
const port = process.env.PORT || 3000;

async function main() {
  try {
    await app.prepare();
    const server = express();
    server.all("*", (req: Request, res: Response) => {
      return handle(req, res);
    });
    server.listen(port, (err?: any) => {
      if (err) throw err;
      console.log(`> Ready on localhost:${port} - env ${process.env.NODE_ENV}`);
    });
  } catch (e) {
    console.error(e);
    process.exit(1);
  }

  const main = new Main();
  const sleep = (ms:number) => new Promise((resolve) => setTimeout(resolve, ms));
  // メインループ
  while(true){
    main.Frame();
    await sleep(0);
  }
}

main();

公式のカスタムサーバーと基本は同じですが、typescript対応と、末尾にメインループが入ってます。

メインループ本体は server/main.ts に以下の様に記入します。

export class Main{
    constructor(){
        // 初期化など
    }
    public Frame() {
        // ロジックの実装
    }
}

Next.jsはカスタムサーバーのtypescriptファイルをビルドしてくれないので、その部分のビルドは自分で組み込むために tsconfig.server.json ファイルを作成して以下の様に記述。

{
    "extends": "./tsconfig.json", // tsconfig.jsonの設定を継承する
    "compilerOptions": {
      "module": "commonjs", // Next.jsとExpressの両方を連携させる
      "outDir": "dist", // ビルドファイルの出力先
      "noEmit": false // Next.jsはBebelを使用してTypeScriptをコンパイルするので、TSコンパイラはjsを出力しない
    },
    "include": ["server/**/*.ts"] // serverフォルダ以下をコンパイルさせる
}

実行中のファイル更新監視をするために nodemon.json を作成して以下の様に記述。

{
    "watch": ["server"],
    "exec": "ts-node --project tsconfig.server.json server/index.ts",
    "ext": "js ts"
}

package.json の scripts を以下のように変更

    "dev": "nodemon",
    "build:next": "next build",
    "build:server": "tsc --project tsconfig.server.json",
    "start": "NODE_ENV=production node dist/index.js",

これで main.ts の Frame 内にconsole.logなど書いて実行すれば無限に出力されるはずです。