DetaにNestJS、Angularの成果物をプッシュする手順 

# DetaにNestJS、Angularの成果物をプッシュする手順  

## Detaとは  
当方はHeroku無料枠廃止に伴うニュースから代替となるサービスの記事で知りました。感謝です。  
- [Herokuの無料枠が廃止になるのでDeta.shへ移行する](https://qiita.com/namachan/items/b8c1ee89dc091d656d4f)  
- [完全無料のDeta Cloudを利用して画像ファイルのアップロード/ダウンロードサイトを作ってみた](https://qiita.com/kitazaki/items/b1b19aa997b6a1437f7f)  
  
Detaは無料でアプリをデプロイできるサービスです。  
[Deta Space](https://deta.space)
> (Google翻訳) 
> ...
> Space では開発は無料です。私たちは、開発者が他の人のために自分のアイデアを実行するコンピューターを構成、管理したり、料金を支払ったりする必要はないと考えています。また、エンドユーザーはデフォルトで自分のものに対して自由に開発できるべきだと考えています。
> ...
> 
> (原文)  
> ...With Space, development is free. We don’t think developers should have to configure, manage, or pay for the computer that runs their idea for other people. We also think that end-users should have the freedom to develop against their own stuff, by default.
> ...  
> [For doers & dreamers](https://deta.space/motivation/)

パーソナルクラウドなる標語を提唱しており、当方の語彙力ではお伝え出来ないですが、AWS、Azure、GCPとは異なる思想を持っているようです。失礼を承知で申し上げると、そこはかとなく漂うB級感がとても好みです。まだまだマイナーな存在のようで、Googleで`deta`を検索しても検索結果に公式が上がってきません。   

下図は[https://deta.space/motivation/](https://deta.space/motivation/)の画面の抜粋です。  
![キャプチャ (小).GIF](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261708/1b13f39c-aec6-1384-58c9-45c4884235c1.gif)



## 当記事のきっかけ  
[The Space Runtime](https://deta.space/docs/en/build/fundamentals/the-space-runtime)  
上記Detaのドキュメントに
> (Google翻訳)Deta Space には、ほぼすべての種類のアプリを実行できる独自のランタイムである Space Runtime があります。たとえば、Space Runtime は以下をサポートします。
> - React、Vue、Svelteなどのフレームワークで構築されたフロントエンド サイト
> - Next、Nuxt、SvelteKitなどのフルスタック フレームワーク
> - Node.js、Python、さらには Go、Rust、またはよりカスタムなもので構築されたバックエンド アプリ  
> 
> 個々の Space アプリは、これらのさまざまなテクノロジーを組み合わせて構築できます。たとえば、SvelteKit アプリと Go API、Next.js アプリと Python API など、最大 5 つの異なる言語とフレームワークを組み合わせて構築できます。  
> (原文)Deta Space has its own runtime, the Space Runtime, that can run almost any type of app. For example, the Space Runtime supports:
> - frontend sites built with frameworks like React, Vue, and Svelte
> - full-stack frameworks like Next, Nuxt, or SvelteKit
> - backend apps built with Node.js, Python and even Go, Rust or something more custom
> 
> An individual Space app can be built by combining these different technologies, for example, a SvelteKit app with a Go API or a Next.js app with a Python API — really any combination of up to 5 different languages and frameworks.

とあります。NestJSはNode.jsで構築されたバックエンドに含まれると思います。かなしいかなAngularが列挙されていません。  
当記事は自分なりに試してサーバーサイドNestJS、フロントサイドAngularの成果物をDetaにプッシュした手順を記します。  

## 注意  
Detaのアカウントの作成が済んでいる状態からの手順になります。  
当記事では、成果物を外部に公開するところまでは試していません。  
当記事の手順で進めたのち、　　
```bash
space release
```
コマンドを実行すると、公開できるはずです。  
試しに実行してみたところ、以下のように聞かれました。 
> Releasing makes your app available via a unique link for others to install and use.
> If you only want to use this app yourself, use your Builder instance instead.
> ? Are you sure you want to release this app to others? (y/n) n
> Aborted releasing this app.  
> (Google翻訳)  リリースすると、他の人が独自のリンクを介してアプリをインストールして使用できるようになります。
このアプリを自分だけで使用したい場合は、代わりに Builder インスタンスを使用してください。
>

実行したとてAngularのテンプレートアプリが表示されるだけなので、それを公開するというのも忍びなく、キャンセルしてしましました。いずれ自信をもって公開できるもの作ろうと思います。  
以上の通り、公開までは試しておりません。それでもよろしければ読み進めていただければと思います。  

## 環境  
Windows10 
WSLストア版

## 手順  
まずは理屈はおいておいて、当方がDetaにNestJS+Angularの成果物をDetaにプッシュできた手順を記し、後に設定の根拠を記します。  
### Dockerfile
```:Dockerfile
FROM node:16.20.1

ENV LANG=C.UTF-8
ENV LANGUAGE=en_US:

RUN apt-get update && apt-get install nano

# Space CLIのインストールと環境変数の設定
RUN curl -fsSL https://deta.space/assets/space-cli.sh | sh
ENV PATH $PATH:/root/.detaspace/bin
RUN echo $PATH

RUN mkdir /app
WORKDIR /app
```
### Dockerイメージ作成  
イメージ名`deta`、タグ`node-16.20.1`でDockerイメージを作成しました。  
```bash
docker build -t deta:node-16.20.1
```
### Dockerコンテナ作成  
```bash
docker run --name deta-nest-angular -v /development/deta-nest-angular:/app -w /app -p 4200:4200 -p 3000:3000  -itd --dns 8.8.8.8 deta:node-16.20.1
```
### DetaSpaceのアクセストークンを取得    
DetaSpaceのアクセストークンを取得します。  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261708/825002ec-26f1-a585-f49b-6c924ffb2294.png)

`Generate Token`をクリックします。  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261708/a5f1dbb2-9c0a-96c2-538b-c42061120482.png)
  
生成されたトークンが表示されます。トークンが表示されるタイミングはこの時のみです。外部に漏れないように控えてください。  

### 開発環境とDetaを連結  
以下はコンテナ内の作業です。  
`space login`コマンドを実行し、DetaSpaceにログインします。  
```bash
space login

# 以下のようにトークンを聞かれるので、上の手順で取得したアクセストークンをコピペします。
To authenticate the Space CLI with your Space account, generate a new access token in your Space settings and paste it below:

? Enter access token (0 chars) >

# トークンを入力してアクセスに成功すると以下の表記になりました。  
? Enter access token (41 chars) > ***************************************** 
👍 Login Successful!

Checking for new Space CLI version...
                                                                     
i New Space CLI version available, upgrade with space version upgrade
```
### コンテナ内での作業  
VSCodeで作成したコンテナ内に入ります。  
以下のように構成していきます。  
```
app/
  ├front-side/       # Angularのプロジェクトディレクトリ
  │  ├node_modules/
  │  ├package.json
  │  ...
  ├server-side/      # NestJSのプロジェクトディレクトリ
  |  ├app/           # Angularのデプロイ先
  │  ├node_modules/
  │  ├package.json
  │  ...  
  ├node_modules/     # プロジェクトのルートディレクトリに@angular/cli、@nestjs/cliをインストール
  ├package.json
  └Spacefile         # DetaSpaceの設定ファイル
```
```bash
# プロジェクト初期化
npm init -y
# プロジェクトのルートディレクトリで@angular/cliと@nestjs/cliをインストールします。  
npm install --save-dev @angular/cli @nestjs/cli
# Angularのテンプレートプロジェクトを生成します。
npx ng new front-side
# NestJSのテンプレートプロジェクトを生成します。
npx nest new server-side
```
Angluarのビルド先をNestJSのプロジェクトディレクトリ直下に設定します。  
```json:angular.json
{
  ...
  "projects": {
    "front-side": {
      ...
      "architect": {
        "build": {
          ...
          "options": {
            "outputPath": "../server-side/app" #←ここ
          }
        }
      }
    }
  }
}
```
NestJSでAngularのビルド結果を提供できるように、`@nestjs/serve-static`をインストールし、設定します。  
```bash
cd /app/server-side

npm install @nestjs/serve-static
```
`app.module.ts`を修正します。  
```typescript:server-side/src/app.module.ts
...
import { ServeStaticModule } from '@nestjs/serve-static'
import { join } from 'path';

@Module({
  imports: [
    ServeStaticModule.forRoot({
      rootPath: join(__dirname, '../app'), 
    })
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
```
ブラウザでNestJSで作成したサーバーにアクセスし、Angularで作成した画面が表示されるか確認します。  
フロントサイドをビルドします。先ほどの`angular.json`でビルド先を変更したので、`server-side/app`ディレクトリにビルドされます。    
```bash
cd /app/front-side
npm run build
```
`localhost:3000`にアクセスしたとき、`Hello world!`の表示ではなくAngularの成果物が表示されるように、`server-side/src/app.controller`を修正します。  
```typescript:server-side/src/app.controller.ts
@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  /* この部分をコメントアウトします。
  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
  */
}
```
またサーバーを起動する際のポートは環境変数から取得するように`server-side`修正します。  
Detaで実行する為に必要です。  
> (Google翻訳) アプリがPORT環境変数で定義されたポートをリッスンするように構成されていることを確認してください。  
> (原文💡 Make sure your app is configured to listen on the port defined by the PORT environment variable.)  
> [Deta公式\(Run a Node.js App\)](https://deta.space/docs/en/build/quick-starts/node)
```typescript:server-side/src/main.ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  // ポートを環境変数から取得するようにする。
  const port = process.env.PORT || 3000;
  await app.listen(port);
}
```

NestJSのテストサーバーを起動します。  
```bash
cd /app/serve-side
npm run start:dev
```
ブラウザで`http://localhost:3000`にアクセスしAngularの画面が表示されます。  

NestJSのビルド時、Webpackを使用して単一ファイルになるように設定します。  
※デフォルトの状態でビルドしたものだと、Detaにアップロード後実行できませんでした。単一ファイルになる様にし、`node server-side/main.js`で起動できるようにすることで実行できました。  
```json:server-side/package.json
{
  ...
  "scripts": {
    "build": "nest build --builder webpack", 
    "start:dev": "nest start --watch --builder webpack", 
    ...
  }
}
```
```javascript:server-side/webpack.config.js
const { NODE_ENV = 'production' } = process.env;
module.exports = {
  target: 'node',
  mode: NODE_ENV,
  externals: [
    {
      '@nestjs/websockets/socket-module': 
        'commonjs2 @nestjs/websockets/socket-module',
      '@nestjs/microservices/microservices-module': 
        'commonjs2 @nestjs/microservices/microservices-module',
      '@fastify/static': 'commonjs2 @fastify/static'
    },
  ],
  optimization: {
    minimize: false,
  },
};
```
プロジェクトのルートディレクトリから、ビルド、開発サーバーでの起動、通常起動が出来るように`package.json`の`scripts`を設定します。ここで設定したスクリプトをこの後にでてくる`Spacefile`に設定します。  
```json:/app/package.json
{
  ...
  "scripts": {
    "build": "npm run build:front-side && npm run build:server-side", 
    "build:front-side": "cd front-side && npm run build", 
    "build:server-side": "cd server-side && npm run build", 
    "start:dev": "npm run dev:front-side & npm run dev:server-side", 
    "dev:front-side": "cd front-side && ng serve --host 0.0.0.0 --poll 2000", 
    "dev:server-side": "cd server-side && npm run start:dev", 
    "start": "node server-side/dist/main.js"
  }, 
  ...
}
```
### `space new`コマンドを実行  
コンテナ内の作業です。  
プロジェクトルートディレクトリで、`space new`コマンドを実行します。  
```bash
# プロジェクトルートディレクトリに移動
cd /app

space new

# 生成するプロジェクト名を聞かれるので入力します。
# 当例ではfirst-deta-appとしました。
? What is your projects name? > first-deta-app

# yを選択します。
? Do you want to setup "first-deta-app" with this configuration? (Y/n)
```
処理が完了すると、`.space`ディレクトリ、`Spacefile`が作成され、.gitignoreに`.space`が追記されます。  
`Spacefile`を以下のように修正します。  
```:Spacefile
v: 0
micros: 
  - name: first-deta-app
    src: ./server-side
    engine: nodejs16
    include:
      - app
      - dist
    run: node dist/main.js
    commands: 
      - npm run build
    dev: npm run start:dev
    primary: true
    public: true
```

## `space push`コマンドを実行  
いよいよ、Detaにpushします。  
```bash
space push

# 成功するとURLが表示されます。  
Validating your Spacefile...
                                                     
Your Spacefile looks good, proceeding with your push!

✓ Successfully started your build!
✓ Successfully pushed your Spacefile!
...
省略
...
✓ Successfully pushed your code and updated your Builder instance!
Builder instance: https://firstdetaapp-1-t7712186.deta.app
```
表示されるURLにアクセスすると、Angularの画面が表示されるはずです。 
またdeta.spaceのキャンバス画面を確認すると、開発中のアプリのアイコンが追加されています。  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261708/03494ea1-b371-c62b-0ed7-c75c0a51c3ad.png)

  
以上です。  

## 設定の根拠  
### Dockerfile  
```:Dockerfile(再掲)
FROM node:16.20.1

ENV LANG=C.UTF-8
ENV LANGUAGE=en_US:

RUN apt-get update && apt-get install nano

# Space CLIのインストールと環境変数の設定
RUN curl -fsSL https://deta.space/assets/space-cli.sh | sh
ENV PATH $PATH:/root/.detaspace/bin
RUN echo $PATH

RUN mkdir /app
WORKDIR /app
```
Dockerイメージにnode:16.20.1を選定した理由は、[公式ドキュメント](https://deta.space/docs/en/build/quick-starts/node#conten)に以下のような記載がありました。  
> (Google翻訳)  💡 現在、Space は Node.js 16 のみをサポートしていますが、間もなく Node.js 18 のサポートを追加する予定です。nvmなどのノード バージョン マネージャーを使用して、コンピューターに Node.js と npm をインストールすることをお勧めします。これにより、さまざまなバージョンの Node.js をすばやくインストールして使用できるようになります。  
> (原文)💡 Currently, Space only supports Node.js 16, with plans to add support for Node.js 18 soon. We recommend using a Node version manager like nvm to install Node.js and npm on your computer. This will allow you to quickly install and use different versions of Node.js.

DockerHubでNode.js16系統で一番新しいバージョンナンバーのイメージを選びました。  
`Space CLIのインストールと環境変数の設定`の部分は[公式サイトのこちら](https://deta.space/docs/en/build/fundamentals/space-cli)にあるようにDeta Space用のアプリを構築するのに必要なため、Dockerコンテナ生成時にSpace CLIがインストールされるように設定しました。  
下に続く環境変数の設定ですが、公式にある通り`RUN curl -fsSL https://deta.space/assets/space-cli.sh | sh`コマンドでインストールすると、以下のように環境変数の設定を促すメッセージが表示されます。
```
  inflating: /root/.detaspace/bin/space
Deta Space CLI was installed successfully to /root/.detaspace/bin
Manually add /root/.detaspace/bin to your path:
  export PATH="/root/.detaspace/bin:$PATH"

  Run 'space --help' to get started
```
そのため環境変数の設定を含めました。  

### NestJSについてWebpackでビルドする理由  
通常通りビルドしてDetaにプッシュすると、下図のようなエラーが発生しました。  
`server-side/package.json`とプロジェクトルートの`package.json`の`scripts`を元の状態に近い以下のようにしてpushしてみます。  
```json:/app/server-side/package.json
{
  ...
  "scripts": {
    //"build": "nest build --webpack", 
    "build": "nest build", 
    ...
  }
}
```
```json:/app/package.json
{
  ...
  "scripts": {
    ...
    //"start": "node server-side/dist/main.js", 
    "start": "cd server-side && nest start", 
    ...
  }
}
```

```bash
space push
```
プッシュ自体は成功しますが、URLにアクセスすると、以下の表示になります。  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261708/3c48076b-33f7-d4d5-639c-81528851399e.png)
  
webpackを用いて単一ファイルになる様にビルドし、`node server-side/dist/main.js`コマンドで起動するようにしたところ、回避されました。  
webpackの設定は下記のサイトを参考にさせていただきました。  
[NestJSアプリケーションをwebpackでBundle【> 404 motivation not found】](https://tech-blog.s-yoshiki.com/entry/260)  

### webpack.confi.jsのexlternalsへの記載方法  
`webpack.config.js`を再掲します。  
先述の[参考サイト](https://tech-blog.s-yoshiki.com/entry/260)に載っていたものを元に作っております。  
当方、externalsに載っている項目はどのように設定しているのかわかりませんでした。  
```javascript:server-side/webpack.config.js
const { NODE_ENV = 'production' } = process.env;
module.exports = {
  target: 'node',
  mode: NODE_ENV,
  externals: [
    {
      '@nestjs/websockets/socket-module': 
        'commonjs2 @nestjs/websockets/socket-module',
      '@nestjs/microservices/microservices-module': 
        'commonjs2 @nestjs/microservices/microservices-module',
      '@fastify/static': 'commonjs2 @fastify/static'
    },
  ],
  optimization: {
    minimize: false,
  },
};
```
ChatGPT大先生に伺ったところ、ビルドしてエラーになったものを記述する、とのことでした。  
試しに`externals`を空にしてビルドを実行してみます。  
```javascript:server-side/webpack.config.js
...
module.exports = {
  target: 'node',
  mode: NODE_ENV,
  externals: [
    {
      //空
    },
  ], 
...
```
server-sideディレクトリで`npm run build`を実行します。  
```bash
npm run build

# 以下のエラーが発生します。  
> server-side@0.0.1 build
> nest build --builder webpack

ERROR in ./node_modules/@nestjs/core/nest-application.js 19:107-150
Module not found: Error: Can't resolve '@nestjs/websockets/socket-module' in '/app/server-side/node_modules/@nestjs/core'
 @ ./node_modules/@nestjs/core/index.js 26:21-50
 @ ./src/main.ts 3:15-38

ERROR in ./node_modules/@nestjs/core/nest-application.js 20:124-177
Module not found: Error: Can't resolve '@nestjs/microservices/microservices-module' in '/app/server-side/node_modules/@nestjs/core'
 @ ./node_modules/@nestjs/core/index.js 26:21-50
 @ ./src/main.ts 3:15-38

ERROR in ./node_modules/@nestjs/serve-static/dist/loaders/fastify.loader.js 19:113-139
Module not found: Error: Can't resolve '@fastify/static' in '/app/server-side/node_modules/@nestjs/serve-static/dist/loaders'
 @ ./node_modules/@nestjs/serve-static/dist/index.js 20:13-48
 @ ./node_modules/@nestjs/serve-static/index.js 6:9-26
 @ ./src/app.module.ts 13:23-54
 @ ./src/main.ts 4:21-44
```
エラー内容から、
- `@nestjs/websockets/socket-module`
- `@nestjs/microservices/microservices-module`
- `@fastify/static`  

を拾い、それを`webpack.config.js`の`externals`に列挙します。  
`エラーのあったモジュール名` : `commonjs2 ○○(○○はエラーのあったモジュール名)`  
という形式で列挙します。  
```javascript:server-side/webpack.config.js
...
  externals: [
    {
      '@nestjs/websockets/socket-module': 
        'commonjs2 @nestjs/websockets/socket-module',
      '@nestjs/microservices/microservices-module': 
        'commonjs2 @nestjs/microservices/microservices-module',
      '@fastify/static': 'commonjs2 @fastify/static'
    }
  ]
...
```
以上です。  

## 感想等  
Detaの公式を読むと(原文は英語で私読めないのでGoogle翻訳して読んでいます。)、なにかこう、とても応援したくなるサービスです。  
記事を書いている2023/07/05現在、[公式ブログ](https://deta.space/blog)の最新は2023/2/23で、[What's new](https://deta.space/changelog)の最新は2023/04/27なので、あまり活発とは見受けられないのですが、どんどん発展して欲しいと願っています。