Rumah > hujung hadapan web > tutorial js > Pengaturcaraan Fungsian dengan fp-ts dalam Node.js

Pengaturcaraan Fungsian dengan fp-ts dalam Node.js

Linda Hamilton
Lepaskan: 2024-12-31 01:08:17
asal
417 orang telah melayarinya

Functional Programming with fp-ts in Node.js

pengenalan

Pengaturcaraan fungsional (FP) telah mendapat populariti kerana kebolehkomposisian, kebolehujian dan keteguhannya. Dalam ekosistem JavaScript, perpustakaan seperti fp-ts membawa konsep FP yang berkuasa kepada TypeScript, membolehkan anda menulis kod yang lebih bersih dan lebih dipercayai.

Artikel ini meneroka fp-ts konsep seperti Option, Either, Task, Reader dan ReaderTaskEither. Kami akan membina apl CRUD asas menggunakan fp-ts, pg (klien PostgreSQL) dan Express.js untuk melihat bagaimana abstraksi ini bersinar secara nyata- aplikasi dunia.


Konsep Utama

Sebelum menyelam ke dalam apl, mari kita bincangkan secara ringkas konsep utama:

  1. Pilihan: Memodelkan kehadiran atau ketiadaan nilai (Sesetengah atau Tiada).
  2. Sama ada: Mewakili pengiraan yang boleh berjaya (Kanan) atau gagal (Kiri).
  3. Tugas: Mewakili pengiraan tak segerak yang malas.
  4. Pembaca: Menyuntik kebergantungan ke dalam pengiraan.
  5. ReaderTaskEither: Menggabungkan Pembaca, Tugas dan Sama ada untuk operasi async dengan kebergantungan dan pengendalian ralat.

Menyediakan Projek

Mulakan Projek

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
Salin selepas log masuk
Salin selepas log masuk

Sediakan TypeScript

Buat tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "outDir": "dist",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["src/**/*"]
}
Salin selepas log masuk
Salin selepas log masuk

Struktur Projek

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
Salin selepas log masuk

Melaksanakan Aplikasi CRUD

Persediaan Pangkalan Data (db.ts)

import { Pool } from 'pg';

export const pool = new Pool({
  user: 'postgres',
  host: 'localhost',
  database: 'fp_ts_crud',
  password: 'password',
  port: 5432,
});
Salin selepas log masuk

Tentukan Model dan Pengesahan (model/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;
};
Salin selepas log masuk

Pengendalian Ralat Tersuai (ralat/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);
};
Salin selepas log masuk

Lapisan Perkhidmatan (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'));
  }
};
Salin selepas log masuk

Operasi CRUD (pengawal/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)
      )
  );
};
Salin selepas log masuk

Express 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');
});
Salin selepas log masuk

Menjalankan Apl dengan Docker dan Docker Compose

Dockerfile

# 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"]
Salin selepas log masuk

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:
Salin selepas log masuk

Jalankan Apl - Mod Pembangunan

# Start the database
docker-compose up -d

# Run the app
npx ts-node-dev src/index.ts
Salin selepas log masuk

Jalankan Apl - Mod Pengeluaran

# 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
Salin selepas log masuk

Ujian Penulisan

Sediakan Jest

Kemas kini skrip 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
Salin selepas log masuk
Salin selepas log masuk

Contoh Ujian (__tests__/UserService.test.ts)

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "outDir": "dist",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["src/**/*"]
}
Salin selepas log masuk
Salin selepas log masuk

Kesimpulan

Dengan memanfaatkan fp-ts, Docker dan pengendalian ralat yang mantap, kami membina aplikasi CRUD Node.js yang berfungsi, berskala dan boleh diselenggara. Menggunakan corak pengaturcaraan berfungsi menjadikan kod anda lebih boleh diramal dan boleh dipercayai, terutamanya apabila mengendalikan aliran kerja tak segerak.

Atas ialah kandungan terperinci Pengaturcaraan Fungsian dengan fp-ts dalam Node.js. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

sumber:dev.to
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Artikel terbaru oleh pengarang
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan