Tutorial: Integrating with Node.js/Express
This tutorial shows you how to integrate AIA with a Node.js/Express application.
#Prerequisites
- Node.js 18+ or Bun
- Express application
- AIA installed and running (Install Guide)
- GitHub repository for your app
#Step 1: Install OpenTelemetry
Terminal
bun add @opentelemetry/api @opentelemetry/sdk-node \
@opentelemetry/auto-instrumentations-node \
@opentelemetry/exporter-trace-otlp-http
#Step 2: Create Instrumentation File
Create instrumentation.ts:
Terminal
import { NodeSDK } from '@opentelemetry/sdk-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT + '/v1/traces',
}),
instrumentations: [getNodeAutoInstrumentations()],
serviceName: process.env.OTEL_SERVICE_NAME || 'my-express-app',
});
sdk.start();
// Graceful shutdown
process.on('SIGTERM', () => {
sdk.shutdown()
.then(() => console.log('Tracing terminated'))
.catch((error) => console.log('Error terminating tracing', error))
.finally(() => process.exit(0));
});
#Step 3: Update Your Server
Update your main server file (server.ts or index.ts):
Terminal
import express from 'express';
const app = express();
const port = process.env.PORT || 3000;
app.use(express.json());
// Your routes
app.get('/api/users', async (req, res) => {
try {
const users = await db.query('SELECT * FROM users');
res.json(users);
} catch (error) {
// Error automatically captured by OTEL
console.error('Failed to fetch users:', error);
res.status(500).json({ error: 'Failed to fetch users' });
}
});
// Test error endpoint
app.get('/api/test-error', (req, res) => {
throw new Error('Test error for AIA demo');
});
// Error handler (important for OTEL to capture errors)
app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
console.error('Unhandled error:', err);
res.status(500).json({ error: err.message });
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
#Step 4: Configure Environment
Create .env:
Terminal
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
OTEL_SERVICE_NAME=my-express-app
PORT=3000
#Step 5: Update package.json
Add start script that loads instrumentation:
Terminal
{
"scripts": {
"dev": "bun run -r ./instrumentation.ts server.ts",
"start": "node -r ./instrumentation.js server.js"
}
}
#Step 6: Test Integration
Terminal
# Start your app
bun run dev
# In another terminal, trigger error
curl http://localhost:3000/api/test-error
#Step 7: Verify in Dashboard
- Open AIA Dashboard:
http://localhost:3000(AIA dashboard, not your app) - See the incident appear
- Wait for autopsy analysis
- Check GitHub for PR
#Common Patterns
Database Errors
Terminal
app.get('/api/users/:id', async (req, res) => {
try {
const user = await db.query(
'SELECT * FROM users WHERE id = ?',
[req.params.id]
);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
} catch (error) {
// Captured by OTEL
console.error('Database error:', error);
res.status(500).json({ error: 'Database error' });
}
});
Async Errors
Terminal
app.post('/api/process', async (req, res) => {
try {
const result = await processData(req.body);
res.json(result);
} catch (error) {
// Captured by OTEL
console.error('Processing error:', error);
res.status(500).json({ error: 'Processing failed' });
}
});
Middleware Errors
Terminal
const authMiddleware = async (req, res, next) => {
try {
const token = req.headers.authorization;
const user = await verifyToken(token);
req.user = user;
next();
} catch (error) {
// Captured by OTEL
res.status(401).json({ error: 'Unauthorized' });
}
};
app.get('/api/protected', authMiddleware, (req, res) => {
res.json({ user: req.user });
});
#Production Deployment
Docker
Create Dockerfile:
Terminal
FROM oven/bun:latest
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --production
COPY . .
ENV OTEL_EXPORTER_OTLP_ENDPOINT=https://your-aia-agent.example.com
ENV OTEL_SERVICE_NAME=my-express-app
CMD ["bun", "run", "-r", "./instrumentation.ts", "server.ts"]
PM2
Create ecosystem.config.js:
Terminal
module.exports = {
apps: [{
name: 'my-express-app',
script: 'server.ts',
interpreter: 'bun',
node_args: '-r ./instrumentation.ts',
env: {
OTEL_EXPORTER_OTLP_ENDPOINT: 'http://localhost:4318',
OTEL_SERVICE_NAME: 'my-express-app',
PORT: 3000
},
env_production: {
OTEL_EXPORTER_OTLP_ENDPOINT: 'https://your-aia-agent.example.com',
OTEL_SERVICE_NAME: 'my-express-app',
PORT: 3000
}
}]
};
Start with PM2:
Terminal
pm2 start ecosystem.config.js --env production
#Troubleshooting
No traces received
Check:
- Instrumentation is loaded before Express:
bun run -r ./instrumentation.ts server.ts - OTEL endpoint is correct:
http://localhost:4318 - AIA Agent is running
- Environment variables are set
Traces sent but no incidents
Check:
- Error status code is 500+
- Error is thrown (not just logged)
- Error handler is in place
- Check AIA Agent logs
Performance impact
OTEL has minimal overhead (~1-2% CPU):
- Traces are sent asynchronously
- Sampling can be configured
- Only errors trigger AIA analysis
#Advanced Configuration
Custom Spans
Terminal
import { trace } from '@opentelemetry/api';
const tracer = trace.getTracer('my-app');
app.get('/api/complex', async (req, res) => {
const span = tracer.startSpan('complex-operation');
try {
const result = await performComplexOperation();
span.setStatus({ code: 0 }); // OK
res.json(result);
} catch (error) {
span.setStatus({ code: 2 }); // ERROR
span.recordException(error);
throw error;
} finally {
span.end();
}
});
Sampling
Terminal
import { TraceIdRatioBasedSampler } from '@opentelemetry/sdk-trace-base';
const sdk = new NodeSDK({
// ... other config
sampler: new TraceIdRatioBasedSampler(0.1), // Sample 10% of traces
});
#Next Steps
- OpenTelemetry Integration - Detailed OTEL guide
- Architecture - How AIA works
- Troubleshooting - Common issues