Practical Applications and Best Practices
Practical Cases
Case 1: Data Validation Pipeline
Build a flexible validator:
typescript
type ValidationResult<T> =
| { success: true; data: T }
| { success: false; errors: string[] }
// Validator factory
const createValidator = <T>() => {
const pipeline = createPipeline<any, ValidationResult<T>>()
const rules: Array<{
name: string
check: (data: any) => boolean
message: string
}> = []
return {
// Add validation rule
rule(name: string, check: (data: any) => boolean, message: string) {
rules.push({ name, check, message })
pipeline.use((data, next) => {
const rule = rules[rules.length - 1]
if (!rule.check(data)) {
return {
success: false,
errors: [`${rule.name}: ${rule.message}`]
}
}
return next(data)
})
return this
},
// Execute validation
validate(data: any): ValidationResult<T> {
return pipeline.run(data, {
onLast: (data) => ({ success: true, data })
})
}
}
}
// Usage
const userValidator = createValidator<User>()
.rule('email',
(d) => /@/.test(d.email),
'Email format is incorrect'
)
.rule('age',
(d) => d.age >= 18,
'Must be at least 18 years old'
)
.rule('password',
(d) => d.password.length >= 8,
'Password must be at least 8 characters'
)
const result = userValidator.validate({
email: 'test@example.com',
age: 25,
password: 'secure123'
})
if (result.success) {
console.log('✅ Validation passed', result.data)
} else {
console.log('❌ Validation failed', result.errors)
}Case 2: State Machine
Implement a simple state machine with Pipeline:
typescript
import { createPipeline, createContext } from 'farrow-pipeline'
type State = 'idle' | 'loading' | 'success' | 'error'
type Event =
| { type: 'FETCH' }
| { type: 'SUCCESS'; data: any }
| { type: 'FAIL'; error: string }
| { type: 'RESET' }
const StateContext = createContext<State>('idle')
const stateMachine = createPipeline<Event, State>()
stateMachine.use((event, next) => {
const currentState = StateContext.get()
// State transition logic
switch (event.type) {
case 'FETCH':
if (currentState === 'idle') {
StateContext.set('loading')
}
break
case 'SUCCESS':
if (currentState === 'loading') {
StateContext.set('success')
}
break
case 'FAIL':
if (currentState === 'loading') {
StateContext.set('error')
}
break
case 'RESET':
StateContext.set('idle')
break
}
return next(event)
})
stateMachine.use(() => {
return StateContext.get()
})
// Usage
console.log(stateMachine.run({ type: 'FETCH' })) // 'loading'
console.log(stateMachine.run({ type: 'SUCCESS', data: {} })) // 'success'
console.log(stateMachine.run({ type: 'RESET' })) // 'idle'Case 3: Request Processing Pipeline
typescript
interface Request {
method: string
url: string
headers: Record<string, string>
body?: any
}
interface Response {
status: number
headers: Record<string, string>
body: any
}
const app = createAsyncPipeline<Request, Response>()
// Logging middleware
app.use((req, next) => {
const start = Date.now()
console.log(`→ ${req.method} ${req.url}`)
const res = next(req)
const duration = Date.now() - start
console.log(`← ${res.status} (${duration}ms)`)
return res
})
// CORS middleware
app.use((req, next) => {
const res = next(req)
return {
...res,
headers: {
...res.headers,
'Access-Control-Allow-Origin': '*'
}
}
})
// Authentication middleware
app.use((req, next) => {
const token = req.headers.authorization
if (!token) {
return {
status: 401,
headers: {},
body: { error: 'Unauthorized' }
}
}
return next(req)
})
// Routing handler
app.use((req) => {
if (req.url === '/users') {
return {
status: 200,
headers: { 'Content-Type': 'application/json' },
body: { users: [] }
}
}
return {
status: 404,
headers: {},
body: { error: 'Not Found' }
}
})Best Practices
✅ 1. Type First
typescript
// ✅ Good: Explicitly specify types
const pipeline = createPipeline<Request, Response>()
// ❌ Bad: Type inference might be inaccurate
const pipeline = createPipeline()✅ 2. Single Responsibility
Each middleware should do only one thing:
typescript
// ✅ Good: Clear responsibilities
pipeline
.use(authMiddleware)
.use(validationMiddleware)
.use(businessLogicMiddleware)
// ❌ Bad: Mixed responsibilities
pipeline.use((req, next) => {
// Auth + validation + business logic all mixed together
const user = authenticate(req)
const valid = validate(req)
const result = processRequest(req, user)
return result
})✅ 3. Use Context Reasonably
Good for Context:
- 🟢 Request-level data shared across middleware (user, request ID)
- 🟢 State that needs isolation
Not good for Context:
- 🔴 Global configuration (use module variables)
- 🔴 Data that can be passed through parameters
typescript
// ✅ Good: Suitable for Context
const UserContext = createContext<User | null>(null)
const RequestIdContext = createContext<string>('')
// ❌ Unnecessary: Module variables are fine
const ConfigContext = createContext({ apiUrl: 'https://api.example.com' })
// Change to this:
const API_URL = 'https://api.example.com'✅ 4. Error Handling
Handle errors at appropriate levels:
typescript
// Global error handling middleware (put at the very beginning)
pipeline.use(async (input, next) => {
try {
return await next(input)
} catch (error) {
console.error('💥 Error occurred:', error)
return { status: 500, error: error.message }
}
})
// Business logic can safely throw errors
pipeline.use(async (input, next) => {
const data = await riskyOperation(input) // Might throw error
return next(data)
})✅ 5. Optimize Performance with Lazy Loading
typescript
// ✅ Good: Lazy load heavy dependencies
pipeline.useLazy(async () => {
const { heavyLib } = await import('./heavy-lib')
return (req, next) => {
if (needsProcessing(req)) {
return next(heavyLib.process(req))
}
return next(req)
}
})
// ❌ Bad: Load at startup
import { heavyLib } from './heavy-lib' // Slows down startup✅ 6. Test-Friendly
Use Container to inject dependencies:
typescript
import { createContainer } from 'farrow-pipeline'
const DatabaseContext = createContext<Database>(realDatabase)
// Inject mock when testing
const testContainer = createContainer({
db: DatabaseContext.create(mockDatabase)
})
const result = pipeline.run(testInput, { container: testContainer })✅ 7. Reusable Middleware Factory
typescript
// Middleware factory
const createLoggerMiddleware = (options: { level: 'info' | 'debug' }) => {
return (input: any, next: any) => {
if (options.level === 'debug') {
console.log('📝 Input:', input)
}
const result = next(input)
console.log('📝 Output:', result)
return result
}
}
// Usage
pipeline.use(createLoggerMiddleware({ level: 'debug' }))⚠️ 8. Browser Environment Not Supported
typescript
// ❌ Cannot use in browser
import { createPipeline } from 'farrow-pipeline' // Will throw error
// ✅ Only use in Node.jsCommon Pitfalls
Pitfall 1: Forgetting to Call next
typescript
// ❌ Error: Forgot to call next
pipeline.use((input, next) => {
console.log('Processing...')
// Forgot to return next(input)
})
// ✅ Correct
pipeline.use((input, next) => {
console.log('Processing...')
return next(input)
})Pitfall 2: Forgetting await in Async
typescript
// ❌ Error: Forgot await
pipeline.use(async (input, next) => {
const result = next(input) // Returns Promise, but no await
console.log(result) // Promise object
return result
})
// ✅ Correct
pipeline.use(async (input, next) => {
const result = await next(input)
console.log(result) // Actual value
return result
})Pitfall 3: Context Lost in Async
typescript
// ❌ Error: AsyncLocalStorage not enabled
UserContext.set(user)
await delay(1000)
const u = UserContext.get() // undefined
// ✅ Correct: Enable async tracking
import * as asyncTracerImpl from 'farrow-pipeline/asyncTracerImpl.node'
asyncTracerImpl.enable()Next Steps
Congratulations! 🎉 Now you've mastered all the core knowledge of farrow-pipeline.
Recommended learning path:
- 📖 Check Complete API Reference - Deep dive into each API
- 🌐 Learn farrow-http - Build HTTP servers
- 🎯 Explore farrow-schema - Type validation
Practice suggestions:
- 🔨 Try Pipeline in small projects
- 📦 Package your common middleware
- 🧪 Write unit tests for middleware
Remember: Good code is like good pipelines - clear, smooth, and doesn't leak! 🚰
