AdonisJS(アドニスJS)についてアウトプット ～機能～

# はじめに
[前回の記事](https://qiita.com/namasa/items/b17bfc1900750b9fae0b)では、AdonisJSについての概要について説明しましたので、今回はMVC周りについて実際にコードを書きながら説明していきます。

# 各スターターキットの初期ディレクトリ構成
まず、AdonisJSでアプリケーションを作成する際にプロジェクトを作成することになります。
**Web、API、Inertia、Slim**のいずれかを選ぶことになりますので、それぞれについて説明していきます。

## Web Starter Kit
サーバーレンダリングするWebアプリの作成をする場合は、こちらを選びます。
**Edge.js**というテンプレートエンジン使用して、画面を作成します。

プロジェクトは以下を選択して作成します。
- **認証**：セッション、アクセストークン、Basic認証のいずれか
- **DB**：Sqlite、PostgreSQL、Mysql、Microsoft SQL Serverのいずれか

ディレクトリ構成としては以下になっています。
```bash
tree -a -L 3 -I node_modules*

.
|-- .editorconfig
|-- .env # 環境変数設定
|-- .env.example
|-- .gitignore
|-- ace.js # aceコマンドの実行ファイル
|-- adonisrc.ts # adonisJSの設定ファイル。プロバイダーやコマンドエイリアスを登録
|-- app
|   |-- exceptions # 例外周りのクラス
|   |   `-- handler.ts
|   |-- middleware # ミドルウェア周りのクラス
|   |   |-- auth_middleware.ts
|   |   |-- container_bindings_middleware.ts
|   |   |-- guest_middleware.ts
|   |   `-- silent_auth_middleware.ts
|   `-- models #モデル周りのクラス
|       `-- user.ts
|-- bin # コマンド周り
|   |-- console.ts
|   |-- server.ts
|   `-- test.ts
|-- config # 設定ファイル
|   |-- app.ts
|   |-- auth.ts # 認証設定
|   |-- bodyparser.ts
|   |-- database.ts # DB設定
|   |-- hash.ts
|   |-- logger.ts # ログ設定
|   |-- session.ts # セッション設定
|   |-- shield.ts # CSRFガード設定
|   |-- static.ts # 静的ミドルウェアの構成
|   `-- vite.ts
|-- database # データベース周り(マイグレーション、シード)
|   `-- migrations
|       `-- 1744190532347_create_users_table.ts
|-- eslint.config.js
|-- package-lock.json
|-- package.json
|-- resources # 静的ファイル
|   |-- css
|   |   `-- app.css
|   |-- js
|   |   `-- app.js
|   `-- views
|       `-- pages
|-- start # アプリケーションの初期スクリプト
|   |-- env.ts
|   |-- kernel.ts
|   `-- routes.ts # ルーティング定義
|-- tests
|   `-- bootstrap.ts
|-- tsconfig.json
`-- vite.config.ts
```


## API starter kit
JSONでやり取りするAPIの作成をする場合はこちらを選びます。
ReactやVueなどでフロントエンドを構築する場合に、AdonisJSバックエンドを作成できます。

プロジェクトは以下を選択して作成します。
- **認証**：セッション、アクセストークン、Basic認証のいずれか
- **DB**：Sqlite、PostgreSQL、Mysql、Microsoft SQL Serverのいずれか

ディレクトリ構成としては以下になっています。
基本的にWebと一緒ですが、```config/やmiddlware/```に違いがあったり、```resources/```がなかったりします。
```bash
tree -a -L 3 -I node_modules*

.
|-- .editorconfig
|-- .env
|-- .env.example
|-- .gitignore
|-- ace.js
|-- adonisrc.ts
|-- app
|   |-- exceptions
|   |   `-- handler.ts
|   |-- middleware
|   |   |-- auth_middleware.ts
|   |   |-- container_bindings_middleware.ts
|   |   |-- force_json_response_middleware.ts
|   |   |-- guest_middleware.ts
|   |   `-- silent_auth_middleware.ts
|   `-- models
|       `-- user.ts
|-- bin
|   |-- console.ts
|   |-- server.ts
|   `-- test.ts
|-- config
|   |-- app.ts
|   |-- auth.ts
|   |-- bodyparser.ts
|   |-- cors.ts
|   |-- database.ts
|   |-- hash.ts
|   |-- logger.ts
|   `-- session.ts
|-- database
|   `-- migrations
|       `-- 1744189966356_create_users_table.ts
|-- eslint.config.js
|-- package-lock.json
|-- package.json
|-- start
|   |-- env.ts
|   |-- kernel.ts
|   `-- routes.ts
|-- tests
|   `-- bootstrap.ts
`-- tsconfig.json
```

## Inertia starter kit
Inertiaは、SPAを構築する方法です。React・Vue・Solid・Svelteを使用して、アプリケーションのフロントエンドを構築できます。


以下を選択してプロジェクトを作成します。
- **認証**：セッション、アクセストークン、Basic認証のいずれか
- **DB**：Sqlite、PostgreSQL、Mysql、Microsoft SQL Serverのいずれか
- **フロントエンド**：Vue3、React、Svelte、Solid.jsのいずれか
- **サーバーサイドレンダリングするか**

ディレクトリ構成としては以下になっています。
基本的にWebと一緒ですが、```config/やresources/```に少し違いがあったり、```inertia/```があったりします。

```bash
tree -a -L 3 -I node_modules*

.
|-- .editorconfig
|-- .env
|-- .env.example
|-- .gitignore
|-- ace.js
|-- adonisrc.ts
|-- app
|   |-- exceptions
|   |   `-- handler.ts
|   |-- middleware
|   |   |-- auth_middleware.ts
|   |   |-- container_bindings_middleware.ts
|   |   |-- guest_middleware.ts
|   |   `-- silent_auth_middleware.ts
|   `-- models
|       `-- user.ts
|-- bin
|   |-- console.ts
|   |-- server.ts
|   `-- test.ts
|-- config
|   |-- app.ts
|   |-- auth.ts
|   |-- bodyparser.ts
|   |-- cors.ts
|   |-- database.ts
|   |-- hash.ts
|   |-- inertia.ts # inertiaの設定
|   |-- logger.ts
|   |-- session.ts
|   |-- shield.ts
|   |-- static.ts
|   `-- vite.ts
|-- database
|   `-- migrations
|       `-- 1744190205575_create_users_table.ts
|-- eslint.config.js
|-- inertia
|   |-- app
|   |   `-- app.ts
|   |-- css
|   |   `-- app.css
|   |-- pages
|   |   |-- errors
|   |   `-- home.vue
|   `-- tsconfig.json
|-- package-lock.json
|-- package.json
|-- resources
|   `-- views
|       `-- inertia_layout.edge
|-- start
|   |-- env.ts
|   |-- kernel.ts
|   `-- routes.ts
|-- tests
|   `-- bootstrap.ts
|-- tsconfig.json
`-- vite.config.ts
```

## Slim starter kit
フレームワークのコア部分とデフォルトのフォルダ構造のみが付属しており、AdonisJSの最小限の機能のみ利用したい場合に使います。

```bash
tree -a -L 3 -I node_modules*

.
|-- .editorconfig
|-- .env
|-- .env.example
|-- .gitignore
|-- ace.js
|-- adonisrc.ts
|-- app
|   |-- exceptions
|   |   `-- handler.ts
|   `-- middleware
|       `-- container_bindings_middleware.ts
|-- bin
|   |-- console.ts
|   |-- server.ts
|   `-- test.ts
|-- config
|   |-- app.ts
|   |-- bodyparser.ts
|   |-- hash.ts
|   `-- logger.ts
|-- eslint.config.js
|-- package-lock.json
|-- package.json
|-- start
|   |-- env.ts
|   |-- kernel.ts
|   `-- routes.ts
|-- tests
|   `-- bootstrap.ts
`-- tsconfig.json

```


ここからは、実際に動かしながらやっていきます。

# コントローラー
AdonisJSはMVCを採用しているので、まずはリクエストをModelとViewに捌くControllerから説明します。
コントローラーを作るために、2つのコマンドが用意されています。

- **1. クラス定義だけあるコントローラー**
- **2. CRUD作成に必要なメソッドが定義されたコントローラー**

【1. クラス定義だけあるコントローラー】を作るには、```node ace make:controller <コントローラー名>```を実行します。

すると、```controllers/```以下にコントローラークラスが作成されます。

```typescript:posts_controller.ts
// import type { HttpContext } from '@adonisjs/core/http'

export default class PostsController {
}
```

【2. CRUD作成に必要なメソッドが定義されたコントローラー】を作るには、```node ace make:controller <コントローラー名> --resource```を実行します。

すると、CRUD用の7つのメソッドが定義された```users_controller.ts```が作成されます。
```typescript:users_controller.ts
import type { HttpContext } from '@adonisjs/core/http'

export default class UsersController {
  /**
   * 一覧取得用(GET)
   */
  async index({}: HttpContext) {}

  /**
   * 新規登録の取得用(GET)
   */
  async create({}: HttpContext) {}

  /**
   * 新規登録用(POST)
   */
  async store({ request }: HttpContext) {}

  /**
   * 詳細取得用(GET)
   */
  async show({ params }: HttpContext) {}

  /**
   * 編集の取得用(GET)
   */
  async edit({ params }: HttpContext) {}

  /**
   * 更新用(PATCH/PUT)
   */
  async update({ params, request }: HttpContext) {}

  /**
   * 削除用(DELETE)
   */
  async destroy({ params }: HttpContext) {}
}
```

## DI(依存性の注入)
DIをする場合は、以下のようにサービスクラスを作成して行います。

```bash:ユーザサービスクラスを作成する
node ace make:service user
DONE:    create app/services/user_service.ts
```

```typescript:user_service.ts
export class UserService {
  // Your code here
}
```

```typescript:users_controller.ts
import { inject } from '@adonisjs/core'
import UserService from '#services/user_service'

@inject()
export default class UsersController {
  constructor(protected userService: UserService) {}

  index() {
    return this.userService.all()
  }
}
```

# ルーティング
さぁ、コントローラーを作成しましたが、このままだとリクエストがコントローラーまで届きませんので、ルーティングを設定しましょう。
ルーティングは```start/routes.ts```に記述します。

例えばpostコントローラーのstoreアクションを定義する場合は以下のようになります。
```typescript:routes.ts
import router from '@adonisjs/core/services/router'

const PostsController  = () => import('#controllers/posts_controller')
router.post('posts', [PostsController , 'store'])
```

リソースコントローラーと呼ばれるAPIのCRUD用のルートを作成する場合は、以下の1行だけでOKです。
```typescript:routes.ts
import router from '@adonisjs/core/services/router'

const UsersController = () => import('#controllers/users_controller')

router.resource('users', UsersController)
```

では、ルートがちゃんと設定されているか確認してみます。
```bash
node ace list:routes

METHOD ROUTE ...................................................... HANDLER MIDDLEWARE
POST   /posts ......................... #controllers/posts_controller.store
GET    /users (users.index) ........... #controllers/users_controller.index
GET    /users/create (users.create) .. #controllers/users_controller.create
POST   /users (users.store) ........... #controllers/users_controller.store
GET    /users/:id (users.show) ......... #controllers/users_controller.show
GET    /users/:id/edit (users.edit) .... #controllers/users_controller.edit
PUT    /users/:id (users.update) ..... #controllers/users_controller.update
PATCH  /users/:id (users.update) ..... #controllers/users_controller.update
DELETE /users/:id (users.destroy) ... #controllers/users_controller.destroy
```
問題なく、設定されていそうですね:smile:

## 実行してみる
ルートが設定されたので、この段階で一度実行してみましょう。

posts_controllerにstoreアクションを追加します。
今回は疎通確認が出来ればいいので、json返すだけのメソッドにします。
```typescript:posts_controller.ts
import type { HttpContext } from '@adonisjs/core/http'

export default class PostsController {
  /**
   * Handle form submission for the create action
   */
  async store({ request }: HttpContext) {
    return {
        'message': 'hello'
    }
  }
}
```

```bash:開発サーバを立ち上げ
node ace serve --hmr
[ info ] starting HTTP server...
╭─────────────────────────────────────────────────╮
│                                                 │
│    Server address: http://localhost:3333        │
│    Watch Mode: HMR                              │
│    Ready in: 1.33 s                             │
│                                                 │
╰─────────────────────────────────────────────────╯
[01:55:09.980] INFO (20204): started HTTP server on localhost:3333
```

開発サーバが立ち上がったので、curlで実行してみます。
正常にjsonが返ってきましたね:smile:
```bash
curl -X POST http://localhost:3333/posts
{"message":"hello"}
```

# モデル
レスポンスが返るになりましたが、実際はDBからデータを取得/登録することがほとんどです。
DBのテーブルを表すクラスがモデルになります。

AdonisJSでは、**Lucid ORM**というKnexベースで作られたActive RecordパターンのORMを利用します。

モデルクラスは```node ace make:model Post```というコマンドで作成できます。
```typescript
import { DateTime } from 'luxon'
import { BaseModel, column } from '@adonisjs/lucid/orm'

export default class Post extends BaseModel {
  @column({ isPrimary: true })
  declare id: number

  @column.dateTime({ autoCreate: true })
  declare createdAt: DateTime

  @column.dateTime({ autoCreate: true, autoUpdate: true })
  declare updatedAt: DateTime
}
```

## DB接続できるようにする
DB接続が必要なのでDockerでPostgreSQLコンテナを立ち上げてみます。
以下のファイルを作成して```docker compose up -d```を実行します。
```yaml:compose.yaml
version: '3.8'
services:
  postgres:
    image: postgres:15
    container_name: postgres15
    environment:
      POSTGRES_USER: root
      POSTGRES_PASSWORD: root
      POSTGRES_DB: app
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
volumes:
  postgres_data:
```

DBコンテナが立ち上がり、DBに接続できることが確認出来ました。
```
docker compsose up -d
docker compose exec postgres bash
psql -U root -d app
```

.envを確認して、DBの接続情報が一致していることを確認します。
```
TZ=UTC
PORT=3333
HOST=localhost
LOG_LEVEL=info
APP_KEY=Ald1bL-tkR8JQaiv1CdxHpd50JBUWLD7
NODE_ENV=development
DB_HOST=127.0.0.1
DB_PORT=5432
DB_USER=root
DB_PASSWORD=root
DB_DATABASE=app
SESSION_DRIVER=cookie
```

## テーブルの作成
次に、Postsテーブルを作成します！
モデル作成時に```-m```を付けるとモデルクラスとマイグレーションクラスをまとめて作ることが出来ます。
```bash:Postモデルの作成
node ace make:model -m Post
```

```models/```以下にモデルクラスと、```database/migrations/```以下にマイグレーションクラスが作成されます。

```typescript:post.ts
import { DateTime } from 'luxon'
import { BaseModel, column } from '@adonisjs/lucid/orm'

export default class Post extends BaseModel {
  @column({ isPrimary: true })
  declare id: number

  @column.dateTime({ autoCreate: true })
  declare createdAt: DateTime

  @column.dateTime({ autoCreate: true, autoUpdate: true })
  declare updatedAt: DateTime
}
```

```typescript:1744340194247_create_posts_table.ts
import { BaseSchema } from '@adonisjs/lucid/schema'

export default class extends BaseSchema {
  protected tableName = 'posts'

  async up() {
    this.schema.createTable(this.tableName, (table) => {
      table.increments('id')
      table.string('name') // 追加

      table.timestamp('created_at')
      table.timestamp('updated_at')
    })
  }

  async down() {
    this.schema.dropTable(this.tableName)
  }
}
```

マイグレーションファイルが作れましたが、まだ実行されていないので、実行してみます。
```bash:マイグレーションの実行
node ace migration:status
┌────────────────────────────────────────────────────────┬───────────┬─────────┬───────────┐
│  Name                                                  │  Status   │  Batch  │  Message  │
├────────────────────────────────────────────────────────┼───────────┼─────────┼───────────┤
│  database/migrations/1744189966356_create_users_table  │  pending  │  NA     │           │
├────────────────────────────────────────────────────────┼───────────┼─────────┼───────────┤
│  database/migrations/1744340194247_create_posts_table  │  pending  │  NA     │           │
└────────────────────────────────────────────────────────┴───────────┴─────────┴───────────┘

node ace migration:run
[ info ] Upgrading migrations version from "1" to "2"
❯ migrated database/migrations/1744189966356_create_users_table
❯ migrated database/migrations/1744340194247_create_posts_table

node ace migration:status
┌────────────────────────────────────────────────────────┬─────────────┬─────────┬───────────┐
│  Name                                                  │  Status     │  Batch  │  Message  │
├────────────────────────────────────────────────────────┼─────────────┼─────────┼───────────┤
│  database/migrations/1744189966356_create_users_table  │  completed  │  1      │           │
├────────────────────────────────────────────────────────┼─────────────┼─────────┼───────────┤
│  database/migrations/1744340194247_create_posts_table  │  completed  │  1      │           │
└────────────────────────────────────────────────────────┴─────────────┴─────────┴───────────┘
```

## データの投入
次にデータを入れたいので以下のコマンドを実行し、```database/seeders```以下にシーダーファイルを作成します。


```bash:postシードの作成
node ace make:seeder Post
DONE:    create database/seeders/post_seeder.ts
```

作成したシードを修正します。
```typescript:post_seeder.ts
import { BaseSeeder } from '@adonisjs/lucid/seeders'
import db from '@adonisjs/lucid/services/db'

export default class extends BaseSeeder {
  async run() {
    // 既存のデータを削除
    await db.from('posts').delete()

    // 新しいデータを挿入
    await db.table('posts').multiInsert([
      {
        name: 'post1',
        created_at: new Date(),
        updated_at: new Date(),
      },
      {
        name: 'post2',
        created_at: new Date(),
        updated_at: new Date(),
      },
      {
        name: 'post3',
        created_at: new Date(),
        updated_at: new Date(),
      }
    ])
  }
}
```

シードを実行し、データを登録します。
```bash
node ace db:seed
❯ completed database/seeders/post_seeder
```

データが入っていることを確認できます。
```sql
app=# select * from posts;
 id |         created_at         |         updated_at
----+----------------------------+----------------------------
  1 | 2025-04-11 03:24:48.378+00 | 2025-04-11 03:24:48.378+00
  2 | 2025-04-11 03:24:48.378+00 | 2025-04-11 03:24:48.378+00
  3 | 2025-04-11 03:24:48.378+00 | 2025-04-11 03:24:48.378+00
(3 rows)
```

## モデルとコントローラーの修正
では、モデルクラスとコントローラクラスを修正し、データの取得/登録が出来るようにしてみましょう。
```typescript:post.ts
import { DateTime } from 'luxon'
import { BaseModel, column } from '@adonisjs/lucid/orm'

interface CreateParams {
  name: string
}

export default class Post extends BaseModel {
  @column({ isPrimary: true })
  declare id: number

  @column()
  declare name: string

  @column.dateTime({ autoCreate: true })
  declare createdAt: DateTime

  @column.dateTime({ autoCreate: true, autoUpdate: true })
  declare updatedAt: DateTime

  static async index() {
    const post = await this.all()
    return { data: post }
  }

  static async register(params: CreateParams) {
    const post = await this.create(params)
    return { data: post }
  }
}
```

```typescript:posts_controller.ts
import type { HttpContext } from '@adonisjs/core/http'
import Post from '#models/post'

export default class PostsController {
  /**
   * 一覧取得
   */
  async index({ response }: HttpContext) {
    const posts = await Post.index()
    return response.status(200).json({
      status: 'success',
      data: posts
    })
  }

  /**
   * 新規登録
   */
  async store({ request, response }: HttpContext) {
    // リクエストボディからデータを取得
    const data = request.only(['name'])

    // 新規投稿を作成
    const post = await Post.register(data)

    return response.status(201).json({
      status: 'success',
      data: post
    })
  }
}
```

一覧取得のルートも追加します
```typescript:routes.ts
import router from '@adonisjs/core/services/router'

const PostsController  = () => import('#controllers/posts_controller')
const UsersController = () => import('#controllers/users_controller')
router.get('posts', [PostsController , 'index'])
router.post('posts', [PostsController , 'store'])
router.resource('users', UsersController)

```

最後にcurlでそれぞれ実行してみます。

```json:一覧取得
curl -X GET http://localhost:3333/posts

{
	"status": "success",
	"data": {
		"data": [
			{
				"id": 3,
				"name": "post3",
				"createdAt": "2025-04-11T04:58:20.938+00:00",
				"updatedAt": "2025-04-11T04:58:20.938+00:00"
			},
			{
				"id": 2,
				"name": "post2",
				"createdAt": "2025-04-11T04:58:20.938+00:00",
				"updatedAt": "2025-04-11T04:58:20.938+00:00"
			},
			{
				"id": 1,
				"name": "post1",
				"createdAt": "2025-04-11T04:58:20.938+00:00",
				"updatedAt": "2025-04-11T04:58:20.938+00:00"
			}
		]
	}
}
```

```json:新規登録
curl -X POST http://localhost:3333/posts \
  -H "Content-Type: application/json" \
  -d '{"name": "post4"}'

{
	"status": "success",
	"data": {
		"data": {
			"name": "post4",
			"createdAt": "2025-04-11T05:02:16.432+00:00",
			"updatedAt": "2025-04-11T05:02:16.432+00:00",
			"id": 4
		}
	}
}
```

```db:登録確認
app=# select * from posts;
 id | name  |         created_at         |         updated_at
----+-------+----------------------------+----------------------------
  1 | post1 | 2025-04-11 05:02:12.186+00 | 2025-04-11 05:02:12.186+00
  2 | post2 | 2025-04-11 05:02:12.186+00 | 2025-04-11 05:02:12.186+00
  3 | post3 | 2025-04-11 05:02:12.186+00 | 2025-04-11 05:02:12.186+00
  4 | post4 | 2025-04-11 05:02:16.432+00 | 2025-04-11 05:02:16.432+00
(4 rows)
```

一覧取得・登録が問題なく出来ましたね:smile:

# バリデーション
登録が出来るようになりましたが、リクエスト値が正しいものなのかチェックしていないので、このままだとセキュリティ的によくないので、入力値チェックをするようにしましょう。

バリデーションクラスを以下のコマンドで作成します。
```bash
node ace make:validator post
create app/validators/post.ts
```

バリデーションルールとして、必須・1文字以上・255文字以内で設定します。
```typescript:app/validators/post.ts
import vine from '@vinejs/vine'

/**
 * Validates the post's creation action
 */
export const createPostValidator = vine.compile(
  vine.object({
    name: vine.string().minLength(1).maxLength(255),
  })
)
```

コントローラーを修正し、登録処理する前にバリデーションを実行するようにします。
```diff_typescript:posts_controller.ts
import type { HttpContext } from '@adonisjs/core/http'
import Post from '#models/post'
import {
  createPostValidator,
} from '#validators/post'

export default class PostsController {
  /**
   * 一覧取得
   */
  async index({ response }: HttpContext) {
    const posts = await Post.index()
    return response.status(200).json({
      status: 'success',
      data: posts
    })
  }

  /**
   * 新規登録
   */
  async store({ request, response }: HttpContext) {
    // リクエストボディからデータを取得
    const data = request.only(['name'])
+   const payload = await createPostValidator.validate(data)

    // 新規投稿を作成
    const post = await Post.register(payload)

    return response.status(201).json({
      status: 'success',
      data: post
    })
  }
}
```

nameをNULLにしてリクエストを実行すると、422エラーになることがわかります。
```bash
curl -X POST -i http://localhost:3333/posts   -H "Content-Type: application/json"   -d '{"name": null}'
HTTP/1.1 422 Unprocessable Entity
set-cookie: adonis-session=s%3AeyJtZXNzYWdlIjoicG90NG1xNmFwb3ZycHBxbjRmdmh4aDN1IiwicHVycG9zZSI6ImFkb25pcy1zZXNzaW9uIn0.UBRNklUSfWGm9RNah_DzIfOc8Pz-qd36CgbC4R5fIAE; Max-Age=7200; Path=/; HttpOnly; SameSite=Lax
x-request-id: vwz8cdv69284va07vttymqy6
content-length: 86
content-type: application/json; charset=utf-8
Date: Fri, 11 Apr 2025 08:26:02 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"errors":[{"message":"The name field is required","rule":"required","field":"name"}]}
```



# ビュー
最後がMVCのViewになり、画面を構成する部分です。
今回はEdgeJS(テンプレートエンジン)を利用して試してみます。

以下のコマンドでviewファイルを作成し、以下のように修正します。
```bash
node ace make:view post
```

```typescript:post.edge 
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
</head>
<body>
  <h1>投稿一覧</h1>

  {{-- テーブル形式での表示 --}}
  <table>
    <thead>
      <tr>
        <th>ID</th>
        <th>名前</th>
      </tr>
    </thead>
    <tbody>
      @each(post in posts)
        <tr>
          <td>{{ post.id }}</td>
          <td>{{ post.name }}</td>
        </tr>
      @else
        <tr>
          <td colspan="3">投稿がありません</td>
        </tr>
      @end
    </tbody>
  </table>
</body>
</html>
```

コントローラーを以下のように修正し、```http://localhost:3333/posts```にアクセスすると、DBのデータが表示されるかと思います:smile:
```typescript:posts_controller.ts
import type { HttpContext } from '@adonisjs/core/http'
import Post from '#models/post'

export default class PostsController {
  /**
   * 一覧取得
   */
  async index({ view }: HttpContext) {
    const posts = await Post.index()
    return view.render('post', {posts})
  }
}
```



![スクリーンショット 2025-04-11 180953.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/95593b98-d7a1-4ff5-a467-3b8e55735437.png)



# コマンド一覧
ここまでちょくちょく出てきたnode aceコマンドですが、ここで[各コマンド](https://docs.adonisjs.com/guides/references/commands#commands-reference)について紹介します。

```bash:全てのコマンドリストを確認
node ace list
```

```bash:開発環境の起動
node ace serve --hmr
```


```bash:AdonisJSアプリケーションのビルド
node ace build
```

```bash:npm installしたパッケージの設定
node ace configure <パッケージ名>
# 例：node ace configure @adonisjs/lucid
```


```bash:パッケージのインストールと設定
node ace add <パッケージ名>
# 例：node ace add @adonisjs/lucid

# 以下2つを1コマンドで行えます
npm install <package-name>
node ace configure
```


```bash:暗号的に安全なランダムキーを生成し、APP_KEYとして.envとして書き込む
node ace generate:key
```


```bash:コントローラーの作成
node ace make:controller <コントローラー名>
# 例：node ace make:controller users

# リソースコントローラー(CRUD)の作成
node ace make:controller <コントローラー名> --resource
# 例：node ace make:controller users --resource
```


```bash:ミドルウェアの作成
node ace make:middleware <ミドルウェア名>
# 例：node ace make:middleware bodyparser
```


```bash:バリデーションクラスの作成
node ace make:validator <バリデーションクラス名>
# 例：node ace make:validator user
```

```bash:サービスクラスの作成
node ace make:service <サービスクラス名>
# 例：node ace make:service invoice
```

```bash:モデルクラスの作成
node ace make:model <モデルクラス名>
例：node ace make:model User

# モデルと一緒にマイグレーションも作成
node ace make:model -m User
```

```bash:シーダークラスの作成
node ace make:seeder <シーダー名>
例：node ace make:seeder Post
```

```bash:例外クラスの作成
node ace make:exception <例外クラス名>
# 例：node ace make:exception commandValidation
```


```bash:コマンドの策氏絵
node ace make:command <コマンド名>
# 例：node ace make:command listRoutes
```


```bash:テストファイルの作成
node ace make:test <テストファイル名> --suite=unit
# 例：node ace make:test user --suite=unit
# tests/unit/user.spec.ts
```


```bash:ルートの確認
node ace list:routes
```


```bash:シードコマンド
# シードの実行
node ace db:seed

# 全てのテーブルのトランケート
node ace db:truncate

# 全てのテーブルの削除
node ace db:wipe
```

```bash:マイグレーション
# 全てのテーブル削除＆マイグレーション実行
node ace migration:fresh

# 全てのマイグレーションのロールバック＆実行
node ace migration:refresh

# 全てのマイグレーションのロールバック
node ace migration:reset

# 特定のマイグレーションのロールバック
node ace migration:rollback

# 未実行のマイグレーションの実行
node ace migration:run

# マイグレーションステータスの確認
node ace migration:status
```

# その他

## VsCode
みなさんVsCodeなどのIDEを利用して、開発しているとは思いますがAdonisJSにも[vscode拡張](https://marketplace.visualstudio.com/items?itemName=jripouteau.adonis-vscode-extension)があるので、開発する際は必ず入れるようにしましょう！

# おわりに
今回はAdonisJSを実際に触りながら説明しました！
まだまだ知名度が低いですが、Laravelに慣れている方は導入がしやすいと思いますので、技術選定の参考にしていただければと思います。