@pineliner/common-services
v1.0.5
Published
Shared server-side Express components and utilities for fulfilment system APIs
Maintainers
Readme
@fulfilment/common-services
Shared components and utilities for the fulfilment system, supporting both server-side (Express APIs) and client-side (frontend applications) usage.
Installation
bun add @fulfilment/common-servicesFeatures
Server-Side
- Authentication Middleware - JWT-based authentication with configurable options
- CORS Middleware - Standardized CORS configuration with presets
- Error Handling - Centralized error handling middleware
- Health Checks - Standardized health check endpoints
- Express App Factory - Pre-configured Express app setup
Client-Side
- API Client - Type-safe HTTP client with authentication support
- Auth Client - Token management and user profile handling
- Utilities - URL building, retry logic, pagination helpers
- Local Storage - Browser storage and cookie utilities
Common
- TypeScript Support - Full type definitions for both server and client
- Shared Types - Common interfaces and types across server/client
Usage
Server-Side Usage
Quick Start - Full App Factory
import { createFullApp } from '@fulfilment/common-services';
const app = createFullApp({
cors: {
origins: ['http://localhost:3000', 'http://localhost:3001'],
credentials: true
},
auth: {
jwtSecret: process.env.JWT_SECRET,
skipPaths: ['/health', '/docs']
},
health: {
serviceName: 'my-api',
version: '1.0.0'
}
});
// Your protected routes
app.use('/api/users', (app as any).auth, userRoutes);
app.listen(3000);Individual Middleware Usage
Authentication
import { createAuthMiddleware, AuthMiddleware } from '@fulfilment/common-services/middleware';
// Simple usage
const auth = createAuthMiddleware({
jwtSecret: process.env.JWT_SECRET,
expiresIn: '24h',
skipPaths: ['/health', '/docs']
});
app.use('/api', auth, apiRoutes);
// Class-based usage for token generation
const authManager = new AuthMiddleware({
jwtSecret: process.env.JWT_SECRET
});
const token = authManager.generateToken({
userId: '123',
email: '[email protected]'
});CORS
import { createCorsMiddleware, corsConfigs } from '@fulfilment/common-services/middleware';
// Development preset
app.use(createCorsMiddleware(corsConfigs.development));
// Custom configuration
app.use(createCorsMiddleware({
origins: ['https://myapp.com'],
methods: ['GET', 'POST'],
credentials: false
}));Error Handling
import { createErrorHandler, notFoundHandler } from '@fulfilment/common-services/middleware';
// 404 handler
app.use(notFoundHandler);
// Error handler
app.use(createErrorHandler({
includeStack: process.env.NODE_ENV === 'development',
logErrors: true,
customLogger: (error, req) => {
console.error('API Error:', error.message, req.path);
}
}));Health Checks
import { createHealthEndpoint } from '@fulfilment/common-services/utils';
const healthCheck = createHealthEndpoint({
serviceName: 'pipeline-api',
version: '1.0.0',
customChecks: [
async () => ({
name: 'database',
status: 'ok', // or 'error'
details: { connectionCount: 5 }
})
]
});
app.get('/health', healthCheck);Manual App Setup
import express from 'express';
import {
createCorsMiddleware,
createAuthMiddleware,
createErrorHandler,
notFoundHandler
} from '@fulfilment/common-services';
const app = express();
// Basic middleware
app.use(express.json());
app.use(createCorsMiddleware());
// Health check (before auth)
app.get('/health', simpleHealthCheck);
// Authentication for API routes
const auth = createAuthMiddleware();
app.use('/api', auth, apiRoutes);
// Error handling
app.use(notFoundHandler);
app.use(createErrorHandler());Type Definitions
import type {
JWTPayload,
AuthenticatedRequest,
HealthCheckResponse,
CORSConfig
} from '@fulfilment/common-services';
// Extend Express Request with auth info
declare global {
namespace Express {
interface Request {
user?: JWTPayload;
tenantId?: string;
}
}
}Migration from Existing Code
To migrate existing API packages:
- Install the common-services package
- Replace auth middleware - Remove local auth.ts files and use the shared version
- Replace CORS setup - Use the shared CORS configuration
- Replace error handlers - Use the shared error handling middleware
- Replace health checks - Use the shared health check utilities
- Update imports - Replace local imports with package imports
Configuration
All middleware accepts configuration objects to customize behavior:
- AuthConfig - JWT secret, expiration, skip paths
- CORSConfig - Origins, headers, methods, credentials
- ErrorHandlerConfig - Stack traces, logging, custom logger
- HealthCheckConfig - Service name, version, custom checks
Client-Side Usage
Import Options
// Option 1: Import from specific client module
import { ApiClient, AuthClient, createApiClient } from '@fulfilment/common-services/client';
// Option 2: Import from main package with namespace
import { client } from '@fulfilment/common-services';
const apiClient = new client.ApiClient({ baseUrl: 'http://localhost:3000' });
// Option 3: Import from server module explicitly
import { authMiddleware } from '@fulfilment/common-services/server';API Client
import { createApiClient, createAuthClient } from '@fulfilment/common-services/client';
// Create API client
const apiClient = createApiClient({
baseUrl: 'http://localhost:3000',
timeout: 10000,
onError: (error) => {
console.error('API Error:', error.message);
}
});
// Basic usage
const response = await apiClient.get('/api/users');
const users = response.data;
// With authentication token
apiClient.setAuthToken('your-jwt-token');
const protectedData = await apiClient.get('/api/protected');
// POST request
const newUser = await apiClient.post('/api/users', {
name: 'John Doe',
email: '[email protected]'
});
// Paginated requests
const paginatedUsers = await apiClient.getPaginated('/api/users', {
page: 1,
limit: 20,
sort: 'created_at',
order: 'desc'
});Authentication Client
import { createAuthClient } from '@fulfilment/common-services/client';
const authClient = createAuthClient({
storage: 'localStorage', // or 'sessionStorage' or 'memory'
tokenKey: 'auth_token',
onTokenExpired: () => {
// Redirect to login or refresh token
window.location.href = '/login';
}
});
// Login and store tokens
const loginResponse = await apiClient.post('/auth/login', {
email: '[email protected]',
password: 'password'
});
authClient.setTokens({
accessToken: loginResponse.data.token,
refreshToken: loginResponse.data.refreshToken,
expiresAt: Date.now() + 3600000 // 1 hour
});
// Check authentication status
if (authClient.isAuthenticated()) {
const userProfile = authClient.getUserProfile();
console.log('User:', userProfile);
}
// Get current token for API requests
const token = authClient.getAccessToken();
apiClient.setAuthToken(token);Client Utilities
import {
buildApiUrl,
withRetry,
createPaginationInfo,
storage,
cookies
} from '@fulfilment/common-services/client';
// URL building
const url = buildApiUrl('http://localhost:3000', '/api/users', {
page: 1,
limit: 20
});
// Result: "http://localhost:3000/api/users?page=1&limit=20"
// Retry logic
const dataWithRetry = await withRetry(
() => apiClient.get('/api/unreliable-endpoint'),
{
maxRetries: 3,
delay: 1000,
backoff: 'exponential',
retryCondition: (error) => error.status >= 500
}
);
// Pagination helpers
const paginationInfo = createPaginationInfo(paginatedResponse);
console.log(`Showing ${paginationInfo.startIndex}-${paginationInfo.endIndex} of ${paginationInfo.totalItems}`);
// Local storage
storage.set('user-preferences', { theme: 'dark', language: 'en' });
const preferences = storage.get('user-preferences');
// Cookies
cookies.set('session-id', 'abc123', {
expires: new Date(Date.now() + 86400000), // 1 day
secure: true,
sameSite: 'strict'
});React Hook Examples
// Custom hook for API calls
import { useEffect, useState } from 'react';
import { createApiClient } from '@fulfilment/common-services/client';
const apiClient = createApiClient({ baseUrl: process.env.REACT_APP_API_URL });
export function useApi<T>(endpoint: string) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
apiClient.get<T>(endpoint)
.then(response => setData(response.data))
.catch(err => setError(err.message))
.finally(() => setLoading(false));
}, [endpoint]);
return { data, loading, error };
}
// Usage in component
function UserList() {
const { data: users, loading, error } = useApi<User[]>('/api/users');
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<ul>
{users?.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}Migration Examples
Server-Side (Express API)
Before:
// Old tracking-api/src/index.ts
import express from 'express';
import cors from 'cors';
import { authMiddleware } from './middleware/auth';
// ... lots of boilerplateAfter:
// New tracking-api/src/index.ts
import { createFullApp } from '@fulfilment/common-services/server';
// or: import { createFullApp } from '@fulfilment/common-services';
const app = createFullApp({
cors: { origins: ['http://localhost:3000'] },
auth: { jwtSecret: process.env.JWT_SECRET },
health: { serviceName: 'tracking-api' }
});Client-Side (React/Vue/etc)
Before:
// Old frontend code
const response = await fetch('/api/users', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
const data = await response.json();After:
// New frontend code
import { createApiClient } from '@fulfilment/common-services/client';
const apiClient = createApiClient({ baseUrl: '/api' });
apiClient.setAuthToken(token);
const { data } = await apiClient.get('/users');Development
# Install dependencies
bun install
# Build the library
bun run build
# Type check
bun run typecheck
# Watch mode for development
bun run dev