@walkeros/server-source-express
v0.4.1
Published
Express server source for walkerOS
Maintainers
Readme
@walkeros/server-source-express
Express server source for walkerOS - turn-key HTTP event collection server with Express.js.
Installation
npm install @walkeros/server-source-expressQuick Start
Standalone Server (Docker-style)
import { startFlow } from '@walkeros/collector';
import { sourceExpress } from '@walkeros/server-source-express';
const { collector } = await startFlow({
sources: {
express: {
code: sourceExpress,
config: {
settings: {
port: 8080, // Start server on port 8080
},
},
},
},
destinations: {
// Your destinations here
},
});
// Server is now running!
// POST http://localhost:8080/collect - JSON event ingestion
// GET http://localhost:8080/collect - Pixel tracking
// GET http://localhost:8080/health - Health checkApp-Only Mode (Custom Integration)
const { collector } = await startFlow({
sources: {
express: {
code: sourceExpress,
config: {
settings: {
// No port = app only, no server started
path: '/events',
cors: false, // Handle CORS with your own middleware
},
},
},
},
});
// Access the Express app
const expressSource = collector.sources.express;
const app = expressSource.app;
// Add custom middleware
app.use(yourAuthMiddleware);
// Add custom routes
app.get('/custom', customHandler);
// Start server manually
app.listen(3000);Configuration
Settings
interface Settings {
/**
* HTTP server port to listen on
* If not provided, server will not start (app-only mode)
* @optional
*/
port?: number;
/**
* Event collection endpoint path
* All HTTP methods (POST, GET, OPTIONS) registered on this path
* @default '/collect'
*/
path?: string;
/**
* CORS configuration
* - false: Disabled
* - true: Allow all origins (default)
* - object: Custom CORS options
* @default true
*/
cors?: boolean | CorsOptions;
/**
* Enable health check endpoints
* - GET /health (liveness check)
* - GET /ready (readiness check)
* @default true
*/
status?: boolean;
}CORS Options
interface CorsOptions {
/** Allowed origins (string, array, or '*') */
origin?: string | string[] | '*';
/** Allowed HTTP methods */
methods?: string[];
/** Allowed request headers */
headers?: string[];
/** Allow credentials (cookies, authorization) */
credentials?: boolean;
/** Preflight cache duration in seconds */
maxAge?: number;
}HTTP Methods
POST - Standard Event Ingestion
Send events as JSON in the request body.
Request:
curl -X POST http://localhost:8080/collect \
-H "Content-Type: application/json" \
-d '{
"event": "page view",
"data": {
"title": "Home Page",
"path": "/"
},
"user": {
"id": "user123"
}
}'Response:
{
"success": true,
"timestamp": 1647261462000
}GET - Pixel Tracking
Send events as query parameters. Returns a 1x1 transparent GIF.
Request:
<!-- In your HTML -->
<img
src="http://localhost:8080/collect?event=page%20view&data[title]=Home&user[id]=user123"
width="1"
height="1"
alt=""
/>Response:
Content-Type: image/gif
Cache-Control: no-cache, no-store, must-revalidate
[1x1 transparent GIF binary]OPTIONS - CORS Preflight
Automatically handled for cross-origin requests.
Request:
curl -X OPTIONS http://localhost:8080/collect \
-H "Origin: https://example.com"Response:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type
204 No ContentHealth Checks
GET /health - Liveness Check
Returns server status (always returns 200 if server is running).
Response:
{
"status": "ok",
"timestamp": 1647261462000,
"source": "express"
}GET /ready - Readiness Check
Returns readiness status (same as health for Express source).
Response:
{
"status": "ready",
"timestamp": 1647261462000,
"source": "express"
}Advanced Examples
Custom CORS Configuration
await startFlow({
sources: {
express: {
code: sourceExpress,
config: {
settings: {
port: 8080,
cors: {
origin: ['https://app.example.com', 'https://admin.example.com'],
credentials: true,
methods: ['GET', 'POST', 'OPTIONS'],
headers: ['Content-Type', 'Authorization'],
maxAge: 86400, // 24 hours
},
},
},
},
},
});Disable Health Checks
await startFlow({
sources: {
express: {
code: sourceExpress,
config: {
settings: {
port: 8080,
status: false, // Disable /health and /ready endpoints
},
},
},
},
});Custom Endpoint Path
await startFlow({
sources: {
express: {
code: sourceExpress,
config: {
settings: {
port: 8080,
path: '/api/v1/events', // Custom path
},
},
},
},
});
// All methods now work on /api/v1/events
// POST /api/v1/events
// GET /api/v1/events
// OPTIONS /api/v1/eventsExtend Express App
const { collector } = await startFlow({
sources: {
express: {
code: sourceExpress,
config: {
settings: { port: 8080 },
},
},
},
});
// Access Express app for advanced customization
const expressSource = collector.sources.express;
const app = expressSource.app;
// Add authentication middleware
app.use('/collect', authMiddleware);
// Add rate limiting
import rateLimit from 'express-rate-limit';
app.use(
'/collect',
rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
}),
);
// Add custom logging
app.use((req, res, next) => {
console.log(`${req.method} ${req.path}`);
next();
});Event Format
Single Event
{
"event": "page view",
"data": {
"title": "Home Page",
"path": "/"
},
"context": {
"language": ["en", 0],
"currency": ["USD", 0]
},
"user": {
"id": "user123",
"device": "device456"
},
"globals": {
"appVersion": "1.0.0"
},
"consent": {
"functional": true,
"marketing": true
}
}Query Parameters (GET)
For pixel tracking, use nested bracket notation:
?event=page%20view
&data[title]=Home%20Page
&data[path]=/
&user[id]=user123
&consent[marketing]=trueThis is automatically parsed by requestToData from @walkeros/core.
Architecture
Infrastructure Ownership
The Express source owns its HTTP infrastructure:
- ✅ Creates Express application
- ✅ Configures middleware (JSON parsing, CORS)
- ✅ Registers routes (POST, GET, OPTIONS)
- ✅ Starts HTTP server (if port configured)
- ✅ Handles graceful shutdown (SIGTERM, SIGINT)
This design enables:
- Turn-key deployment - Just specify a port and deploy
- Docker-friendly - Perfect for containerized environments
- Flexibility - App-only mode for custom integrations
Request Flow
┌─────────────────────────────────────────┐
│ HTTP Client (Browser, Server, etc.) │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ EXPRESS SOURCE │
│ - Receives HTTP request │
│ - Parses body/query params │
│ - Validates request structure │
│ - Calls env.push() → Collector │
│ - Returns HTTP response │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ COLLECTOR │
│ - Event validation & processing │
│ - Consent management │
│ - Mapping rules │
│ - Routes to destinations │
└─────────────────────────────────────────┘Deployment
Docker
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
ENV PORT=8080
EXPOSE 8080
CMD ["node", "server.js"]server.js:
import { startFlow } from '@walkeros/collector';
import { sourceExpress } from '@walkeros/server-source-express';
await startFlow({
sources: {
express: {
code: sourceExpress,
config: {
settings: {
port: process.env.PORT || 8080,
},
},
},
},
// Your destinations...
});Kubernetes
apiVersion: apps/v1
kind: Deployment
metadata:
name: walkeros-collector
spec:
replicas: 3
selector:
matchLabels:
app: walkeros-collector
template:
metadata:
labels:
app: walkeros-collector
spec:
containers:
- name: collector
image: your-registry/walkeros-collector:latest
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 3Testing
The package includes comprehensive tests using mocked Express Request/Response objects.
Run tests:
npm testExample test:
import { sourceExpress } from '@walkeros/server-source-express';
test('should process POST event', async () => {
const mockPush = jest.fn().mockResolvedValue({ event: { id: 'test' } });
const source = await sourceExpress(
{},
{
push: mockPush,
command: jest.fn(),
elb: jest.fn(),
},
);
const req = {
method: 'POST',
body: { event: 'page view', data: { title: 'Home' } },
headers: {},
get: () => undefined,
};
const res = {
status: jest.fn().returnThis(),
json: jest.fn(),
send: jest.fn(),
set: jest.fn(),
};
await source.push(req, res);
expect(res.status).toHaveBeenCalledWith(200);
expect(mockPush).toHaveBeenCalled();
});License
MIT
