0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Express API Docker

Posted at

はじめに

備忘録です。

ExpressでAPI開発
駆け出しの構成から一歩踏み込んだ構成を目指す

本番環境でも耐えられるディレクトリ構成を考える

構成

backend/
  node_modules/
  prisma/
  src/
    config/
      - index.ts
    controllers/
      - indexController.ts
      - userController.ts
    models/
      - index.ts
      - users.ts
    routes/
      - indexRouter.ts
      - userRouter.ts
    services/
      - indexService.ts
      - userService.ts
    - app.ts
    - server.ts
  - .env
  - package-lock.json
  - package.json
  - tsconfig.json
db/
docker/
  backend/
    - Dockerfile
- .dockerignore
- .gitignore
- docker-compose.yml

Dcoker関連

docker/backend/Dockerfile

FROM node:18.14.2-buster

WORKDIR /usr/src/app

COPY ./backend/package*.json ./

RUN npm install
# 本番環境用の場合
# RUN npm install --only=production

COPY ./backend .

# コンテナ起動後にexecで起動するか
# shellで入って起動した方が利便性高そう
# DBが立ち上がる前に接続しようとするので気をつける
# CMD ["npm", "start"]

docker-compose.yml

version: "3.8"
services:
  backend:
    build:
      context: .
      dockerfile: ./docker/backend/Dockerfile
    env_file:
      - ./backend/.env
    tty: true
    depends_on:
      - db
    volumes:
      - ./backend:/usr/src/app
    ports:
      - "3000:3000"
    environment:
      - TZ=Asia/Tokyo
  db:
    image: mysql:8.0.31
    platform: linux/amd64
    env_file: ./db/.env
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    volumes:
      - ./db/data:/var/lib/mysql
      - ./db/conf.d:/etc/mysql/conf.d
    ports:
      - 3306:3306
    environment:
      TZ: Asia/Tokyo
.dockerignore
**/node_modules

プロジェクトのルートファイル

app.ts

import express, { Request, Response } from "express";
import helmet from "helmet";
import cors from "cors";
import compression from "compression";
import morgan from "morgan";
import * as dotenv from "dotenv";
dotenv.config();

import indexRouter from "./routes/indexRouter";
import userRouter from "./routes/userRouter";

const app = express();

app.use(express.json());
app.use(cors());
app.use(helmet());
app.use(compression());
app.use(morgan("dev"));

// Routes
app.use("/", indexRouter);
app.use("/users", userRouter);

export default app;

server.ts

import app from "./app";
import { PORT } from "./config";

app.listen(PORT, () => console.log("サーバーを開始します。"));

routes

routes/userRouter.ts
エンドポイントに対するControllerの振り分けを行う

import express from "express";
import { UsersController } from "../controllers/userController";

// Controllerをインスタンス化
const usersController = new UsersController();
const router = express.Router();

router.get("/", usersController.findAll);
router.post("/", usersController.create);

export default router;

Controller

controllers/userController.ts
try, catchの処理
req.bodyの型定義
tryの中でServiceの呼び出し

import { Request, Response } from "express";
import { UserService } from "../services/userService";
import { NewUser } from "../models";

// Serviceをインスタンス化
const userService = new UserService();

export class UsersController {
  async findAll(req: Request, res: Response) {
    try {
      const users = await userService.findAll();
      res.status(200).json(users);
    } catch (err) {
      res.status(500).json(err);
    }
  }

// req.bodyの型を指定している
  async create(req: Request<{}, {}, NewUser>, res: Response) {
    try {
      const user = await userService.create(req.body);
      res.status(200).json(user);
    } catch (err) {
      res.status(500).json(err);
    }
  }
}

Service

services/userService.ts

import { PrismaClient, User } from "@prisma/client";
import { NewUser } from "../models";

const prisma = new PrismaClient();

// 返り値はprismaからmodelを取得
export class UserService {
  async findAll(): Promise<User[]> {
    return await prisma.user.findMany();
  }

  async create(newUser: NewUser): Promise<User> {
    return await prisma.user.create({
      data: newUser,
    });
  }
}

models

models/users.ts

// req.bodyの型を定義
type NewUser = {
  name: string;
  email: string;
  password: string;
};

config

config/index.ts
環境変数を取得するファイル

export const PORT: number = parseInt(process.env.PORT!, 10) || 3000;

終わりに

よくみる構成に近いと思います。
最近のトレンドはDDDな気がしますが、まずははじめの一歩ということで...
実際は上記の構成にtest/とvalidation/が増えると思います。
Expressのようなディレクトリが用意されていないフレームワークで構成を考えるのは大変です。
ですが、とても勉強になります。Rails等のベストプラクティスの構成をはじめから用意してくれているフレームワークのありがたみが実感できます。
また、フレームワークのディレクトリ構成を見て、何を意図してその構成にしているのか考察できるようになるのかなと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?