函數式程式設計(FP)因其可組合性、可測試性和穩健性而廣受歡迎。在 JavaScript 生態系統中,像是 fp-ts 這樣的函式庫為 TypeScript 帶來了強大的 FP 概念,讓您可以編寫更乾淨、更可靠的程式碼。
本文探討 fp-ts 概念,例如 Option、Either、Task、Reader 和 ReaderTaskEither。我們將使用fp-ts、pg(PostgreSQL 客戶端)和Express.js 建立一個基本的CRUD 應用程序,看看這些抽像在現實中如何閃耀——世界應用。
在深入了解應用程式之前,我們先簡單討論一下主要概念:
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
建立 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
import { Pool } from 'pg'; export const pool = new Pool({ user: 'postgres', host: 'localhost', database: 'fp_ts_crud', password: 'password', port: 5432, });
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; };
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); };
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')); } };
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) ) ); };
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'); });
# 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"]
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
更新 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
{ "compilerOptions": { "target": "ES2020", "module": "CommonJS", "outDir": "dist", "strict": true, "esModuleInterop": true }, "include": ["src/**/*"] }
透過利用 fp-ts、Docker 和強大的錯誤處理,我們建立了一個功能齊全、可擴展且可維護的 Node.js CRUD 應用程式。使用函數式程式設計模式可以讓您的程式碼更加可預測和可靠,尤其是在處理非同步工作流程時。
以上是Node.js 中使用 fp-ts 進行函數式編程的詳細內容。更多資訊請關注PHP中文網其他相關文章!