> 웹 프론트엔드 > JS 튜토리얼 > Node.js에서 fp-ts를 사용한 함수형 프로그래밍

Node.js에서 fp-ts를 사용한 함수형 프로그래밍

Linda Hamilton
풀어 주다: 2024-12-31 01:08:17
원래의
417명이 탐색했습니다.

Functional Programming with fp-ts in Node.js

소개

함수형 프로그래밍(FP)은 구성성, 테스트 가능성 및 견고성으로 인해 인기를 얻었습니다. JavaScript 생태계에서 fp-ts와 같은 라이브러리는 강력한 FP 개념을 TypeScript에 도입하여 더욱 깔끔하고 안정적인 코드를 작성할 수 있게 해줍니다.

이 문서에서는 Option,either, Task, Reader 및 ReaderTaskEither와 같은 fp-ts 개념을 살펴봅니다. fp-ts, pg(PostgreSQL 클라이언트) 및 Express.js를 사용하여 기본 CRUD 앱을 구축하여 이러한 추상화가 실제로 어떻게 빛나는지 살펴보겠습니다. 전세계에 적용됩니다.


주요 개념

앱을 살펴보기 전에 주요 개념을 간략하게 살펴보겠습니다.

  1. 옵션: 값의 존재 여부를 모델링합니다(일부 또는 없음).
  2. 둘 중 하나: 성공(오른쪽)하거나 실패(왼쪽)할 수 있는 계산을 나타냅니다.
  3. 태스크: 지연 비동기 계산을 나타냅니다.
  4. Reader: 계산에 종속성을 삽입합니다.
  5. ReaderTaskEither: 종속성 및 오류 처리가 포함된 비동기 작업을 위해 Reader, Task 및 둘 중 하나를 결합합니다.

프로젝트 설정

프로젝트 초기화

mkdir fp-ts-crud && cd fp-ts-crud
npm init -y
npm install express pg fp-ts io-ts
npm install --save-dev typescript @types/express ts-node-dev jest @types/jest ts-jest
로그인 후 복사
로그인 후 복사

TypeScript 설정

tsconfig.json 만들기:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "outDir": "dist",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["src/**/*"]
}
로그인 후 복사
로그인 후 복사

프로젝트 구조

src/
  index.ts        # Entry point
  db.ts           # Database setup
  models/         # Data models and validation
  services/       # Business logic
  controllers/    # CRUD operations
  utils/          # fp-ts utilities
  errors/         # Custom error classes
로그인 후 복사

CRUD 앱 구현

데이터베이스 설정(db.ts)

import { Pool } from 'pg';

export const pool = new Pool({
  user: 'postgres',
  host: 'localhost',
  database: 'fp_ts_crud',
  password: 'password',
  port: 5432,
});
로그인 후 복사

모델 정의 및 검증(models/User.ts)

import * as t from 'io-ts';
import { isRight } from 'fp-ts/Either';

export const User = t.type({
  id: t.number,
  name: t.string,
  email: t.string,
});

export const validateUser = (data: unknown): t.TypeOf<typeof User> | null => {
  const result = User.decode(data);
  return isRight(result) ? result.right : null;
};
로그인 후 복사

사용자 정의 오류 처리(errors/AppError.ts)

export class AppError extends Error {
  constructor(public statusCode: number, public code: string, public message: string) {
    super(message);
    this.name = 'AppError';
  }
}

export const createAppError = (statusCode: number, code: string, message: string): AppError => {
  return new AppError(statusCode, code, message);
};
로그인 후 복사

서비스 계층(services/UserService.ts)

import { pool } from '../db';
import { ReaderTaskEither, right, left } from 'fp-ts/ReaderTaskEither';
import { pipe } from 'fp-ts/function';
import { createAppError, AppError } from '../errors/AppError';

type Dependencies = { db: typeof pool };
type User = { name: string; email: string };

export const createUser = (
  user: User
): ReaderTaskEither<Dependencies, AppError, string> => (deps) => async () => {
  try {
    const result = await deps.db.query(
      'INSERT INTO users (name, email) VALUES (, ) RETURNING id',
      [user.name, user.email]
    );
    return right(`User created with ID: ${result.rows[0].id}`);
  } catch (error) {
    return left(createAppError(500, 'USER_CREATION_FAILED', 'Failed to create user'));
  }
};

export const getUser = (
  id: number
): ReaderTaskEither<Dependencies, AppError, { id: number; name: string; email: string }> => (deps) => async () => {
  try {
    const result = await deps.db.query('SELECT * FROM users WHERE id = ', [id]);
    return result.rows[0]
      ? right(result.rows[0])
      : left(createAppError(404, 'USER_NOT_FOUND', 'User not found'));
  } catch {
    return left(createAppError(500, 'USER_FETCH_FAILED', 'Failed to fetch user'));
  }
};
로그인 후 복사

CRUD 작업(controllers/UserController.ts)

import { pipe } from 'fp-ts/function';
import { createUser, getUser } from '../services/UserService';
import { pool } from '../db';
import { AppError } from '../errors/AppError';

const errorHandler = (err: unknown, res: express.Response): void => {
  if (err instanceof AppError) {
    res.status(err.statusCode).json({ error: { code: err.code, message: err.message } });
  } else {
    res.status(500).json({ error: { code: 'UNKNOWN_ERROR', message: 'An unexpected error occurred' } });
  }
};

export const createUserHandler = (req: express.Request, res: express.Response): void => {
  pipe(
    createUser(req.body),
    (task) => task({ db: pool }),
    (promise) =>
      promise.then((result) =>
        result._tag === 'Left'
          ? errorHandler(result.left, res)
          : res.json({ message: result.right })
      )
  );
};

export const getUserHandler = (req: express.Request, res: express.Response): void => {
  pipe(
    getUser(parseInt(req.params.id, 10)),
    (task) => task({ db: pool }),
    (promise) =>
      promise.then((result) =>
        result._tag === 'Left'
          ? errorHandler(result.left, res)
          : res.json(result.right)
      )
  );
};
로그인 후 복사

익스프레스 API(index.ts)

import express from 'express';
import { createUserHandler, getUserHandler } from './controllers/UserController';

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

// Routes
app.post('/users', createUserHandler);
app.get('/users/:id', getUserHandler);

// Start Server
app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});
로그인 후 복사

Docker 및 Docker Compose를 사용하여 앱 실행

도커파일

# Stage 1: Build
FROM node:22 AS builder
WORKDIR /app
COPY package*.json .
RUN npm install
COPY . .
RUN npm run build

# Stage 2: Run
FROM node:22
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package*.json ./
RUN npm install --production
CMD ["node", "dist/index.js"]
로그인 후 복사

docker-compose.yml

version: '3.8'
services:
  db:
    image: postgres:15
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
      POSTGRES_DB: fp_ts_crud
    ports:
      - "5432:5432"
    volumes:
      - db_data:/var/lib/postgresql/data
volumes:
  db_data:
로그인 후 복사

앱 실행 - 개발 모드

# Start the database
docker-compose up -d

# Run the app
npx ts-node-dev src/index.ts
로그인 후 복사

앱 실행 - 프로덕션 모드

# Build the docker image
docker build -t fp-ts-crud-app .

# Start the database
docker-compose up -d

# Run the container
docker run -p 3000:3000 fp-ts-crud-app
로그인 후 복사

테스트 작성

설정 Jest

package.json 스크립트 업데이트:

mkdir fp-ts-crud && cd fp-ts-crud
npm init -y
npm install express pg fp-ts io-ts
npm install --save-dev typescript @types/express ts-node-dev jest @types/jest ts-jest
로그인 후 복사
로그인 후 복사

예제 테스트(__tests__/UserService.test.ts)

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "outDir": "dist",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["src/**/*"]
}
로그인 후 복사
로그인 후 복사

결론

fp-ts, Docker 및 강력한 오류 처리를 활용하여 기능적이고 확장 가능하며 유지 관리가 가능한 Node.js CRUD 애플리케이션을 구축했습니다. 함수형 프로그래밍 패턴을 사용하면 특히 비동기 워크플로를 처리할 때 코드를 더욱 예측 가능하고 안정적으로 만들 수 있습니다.

위 내용은 Node.js에서 fp-ts를 사용한 함수형 프로그래밍의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

원천:dev.to
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
저자별 최신 기사
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿