Maison > interface Web > js tutoriel > Programmation fonctionnelle avec fp-ts dans Node.js

Programmation fonctionnelle avec fp-ts dans Node.js

Linda Hamilton
Libérer: 2024-12-31 01:08:17
original
418 Les gens l'ont consulté

Functional Programming with fp-ts in Node.js

Introduction

La programmation fonctionnelle (FP) a gagné en popularité en raison de sa composabilité, de sa testabilité et de sa robustesse. Dans l'écosystème JavaScript, des bibliothèques comme fp-ts apportent de puissants concepts FP à TypeScript, vous permettant d'écrire du code plus propre et plus fiable.

Cet article explore les concepts fp-ts tels que Option, Soit, Tâche, Lecteur et ReaderTaskEither. Nous allons créer une application CRUD de base en utilisant fp-ts, pg (client PostgreSQL) et Express.js pour voir comment ces abstractions brillent en réalité. applications mondiales.


Concepts clés

Avant de plonger dans l'application, discutons brièvement des principaux concepts :

  1. Option : Modélise la présence ou l'absence d'une valeur (Certains ou Aucun).
  2. Soit : représente les calculs qui peuvent réussir (à droite) ou échouer (à gauche).
  3. Tâche : représente les calculs asynchrones paresseux.
  4. Lecteur : Injecte des dépendances dans les calculs.
  5. ReaderTaskEither : combine Reader, Task et Soit pour les opérations asynchrones avec dépendances et gestion des erreurs.

Mise en place du projet

Initialiser le projet

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
Copier après la connexion
Copier après la connexion

Configurer TypeScript

Créez un tsconfig.json :

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "outDir": "dist",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["src/**/*"]
}
Copier après la connexion
Copier après la connexion

Structure du projet

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
Copier après la connexion

Implémentation de l'application CRUD

Configuration de la base de données (db.ts)

import { Pool } from 'pg';

export const pool = new Pool({
  user: 'postgres',
  host: 'localhost',
  database: 'fp_ts_crud',
  password: 'password',
  port: 5432,
});
Copier après la connexion

Définir les modèles et la validation (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;
};
Copier après la connexion

Gestion des erreurs personnalisée (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);
};
Copier après la connexion

Couche de service (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'));
  }
};
Copier après la connexion

Opérations CRUD (contrôleurs/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)
      )
  );
};
Copier après la connexion

API express (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');
});
Copier après la connexion

Exécuter l'application avec Docker et Docker Compose

Fichier Docker

# 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"]
Copier après la connexion

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:
Copier après la connexion

Exécutez l'application - Mode développement

# Start the database
docker-compose up -d

# Run the app
npx ts-node-dev src/index.ts
Copier après la connexion

Exécutez l'application - Mode production

# 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
Copier après la connexion

Tests d'écriture

Configuration de la plaisanterie

Mettre à jour les scripts 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
Copier après la connexion
Copier après la connexion

Exemple de test (__tests__/UserService.test.ts)

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "outDir": "dist",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["src/**/*"]
}
Copier après la connexion
Copier après la connexion

Conclusion

En tirant parti de fp-ts, de Docker et d'une gestion robuste des erreurs, nous avons construit une application Node.js CRUD fonctionnelle, évolutive et maintenable. L'utilisation de modèles de programmation fonctionnels rend votre code plus prévisible et plus fiable, en particulier lors de la gestion de flux de travail asynchrones.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal