Skip to content

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.js

Common 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:

  1. 📖 Check Complete API Reference - Deep dive into each API
  2. 🌐 Learn farrow-http - Build HTTP servers
  3. 🎯 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! 🚰

This is a third-party Farrow documentation site | Built with ❤️ and TypeScript