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.
Sebelum menyelam ke dalam apl, mari kita bincangkan secara ringkas konsep utama:
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
Buat 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
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
{ "compilerOptions": { "target": "ES2020", "module": "CommonJS", "outDir": "dist", "strict": true, "esModuleInterop": true }, "include": ["src/**/*"] }
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!