Deployment Guide
Insiders
Enterprise
Learn how to deploy your Karrio Apps to production with confidence, including testing strategies, CI/CD pipelines, and monitoring.
Deployment Overview
Karrio Apps can be deployed in several ways depending on your architecture and requirements:
Development Setup
Local Development Environment
Clone your app repository1git clone https://github.com/your-org/your-karrio-app.git 2cd your-karrio-app 3 4# Install dependencies 5npm install 6 7# Set up environment variables 8cp .env.example .env.local 9 10# Configure your local environment 11cat > .env.local << EOF 12KARRIO_API_URL=http://localhost:8000 13KARRIO_API_KEY=your-dev-api-key 14JWT_APP_SECRET_KEY=your-jwt-secret 15DATABASE_URL=postgresql://user:pass@localhost:5432/karrio_apps 16REDIS_URL=redis://localhost:6379 17NODE_ENV=development 18EOF 19 20# Start development server 21npm run dev
Project Structure
your-karrio-app/
βββ src/
β βββ components/ # React components
β βββ pages/ # Next.js pages
β βββ api/ # API routes
β βββ lib/ # Utility functions
β βββ hooks/ # Custom hooks
β βββ types/ # TypeScript definitions
βββ tests/
β βββ __mocks__/ # Test mocks
β βββ unit/ # Unit tests
β βββ integration/ # Integration tests
β βββ e2e/ # End-to-end tests
βββ docs/
β βββ README.md
β βββ API.md
β βββ DEPLOYMENT.md
βββ .github/
β βββ workflows/ # GitHub Actions
βββ docker/
β βββ Dockerfile
β βββ docker-compose.yml
β βββ nginx.conf
βββ manifest.ts # App manifest
βββ package.json
βββ tsconfig.json
βββ .env.example
Development Scripts
1{ 2 "scripts": { 3 "dev": "next dev", 4 "build": "next build", 5 "start": "next start", 6 "test": "jest", 7 "test:watch": "jest --watch", 8 "test:e2e": "playwright test", 9 "lint": "eslint . --ext .ts,.tsx", 10 "lint:fix": "eslint . --ext .ts,.tsx --fix", 11 "type-check": "tsc --noEmit", 12 "docker:build": "docker build -t your-app .", 13 "docker:run": "docker run -p 3000:3000 your-app" 14 } 15}
Testing Strategy
Unit Testing
__tests__/components/ShipmentCard.test.tsx1import { render, screen, fireEvent } from "@testing-library/react"; 2import { ShipmentCard } from "../components/ShipmentCard"; 3 4const mockShipment = { 5 id: "1", 6 tracking_number: "TEST123", 7 status: "delivered", 8 recipient: { name: "John Doe" }, 9 created_at: "2024-01-15T10:00:00Z", 10}; 11 12describe("ShipmentCard", () => { 13 it("renders shipment information correctly", () => { 14 render(<ShipmentCard shipment={mockShipment} />); 15 16 expect(screen.getByText("TEST123")).toBeInTheDocument(); 17 expect(screen.getByText("John Doe")).toBeInTheDocument(); 18 expect(screen.getByText("delivered")).toBeInTheDocument(); 19 }); 20 21 it("handles click events", () => { 22 const onClickMock = jest.fn(); 23 render(<ShipmentCard shipment={mockShipment} onClick={onClickMock} />); 24 25 fireEvent.click(screen.getByTestId("shipment-card")); 26 expect(onClickMock).toHaveBeenCalledWith(mockShipment); 27 }); 28 29 it("displays correct status badge", () => { 30 render(<ShipmentCard shipment={mockShipment} />); 31 32 const badge = screen.getByTestId("status-badge"); 33 expect(badge).toHaveClass("bg-green-100"); // Delivered status 34 }); 35});
Integration Testing
__tests__/api/shipments.test.tsx1import { createMocks } from "node-mocks-http"; 2import handler from "../../pages/api/shipments"; 3import { setupTestDatabase, cleanupTestDatabase } from "../helpers/database"; 4 5describe("/api/shipments", () => { 6 beforeAll(async () => { 7 await setupTestDatabase(); 8 }); 9 10 afterAll(async () => { 11 await cleanupTestDatabase(); 12 }); 13 14 it("GET /api/shipments returns shipments list", async () => { 15 const { req, res } = createMocks({ 16 method: "GET", 17 headers: { 18 authorization: "Bearer test-jwt-token", 19 }, 20 }); 21 22 await handler(req, res); 23 24 expect(res._getStatusCode()).toBe(200); 25 const data = JSON.parse(res._getData()); 26 expect(data).toHaveProperty("shipments"); 27 expect(Array.isArray(data.shipments)).toBe(true); 28 }); 29 30 it("POST /api/shipments creates new shipment", async () => { 31 const { req, res } = createMocks({ 32 method: "POST", 33 headers: { 34 authorization: "Bearer test-jwt-token", 35 "content-type": "application/json", 36 }, 37 body: { 38 recipient: { 39 name: "Test User", 40 address_line1: "123 Test St", 41 city: "Test City", 42 state_code: "TS", 43 postal_code: "12345", 44 country_code: "US", 45 }, 46 parcels: [ 47 { 48 weight: 1.5, 49 width: 10, 50 height: 10, 51 length: 10, 52 }, 53 ], 54 }, 55 }); 56 57 await handler(req, res); 58 59 expect(res._getStatusCode()).toBe(201); 60 const data = JSON.parse(res._getData()); 61 expect(data).toHaveProperty("id"); 62 expect(data.recipient.name).toBe("Test User"); 63 }); 64});
End-to-End Testing
tests/e2e/shipment-workflow.spec.ts1import { test, expect } from "@playwright/test"; 2 3test.describe("Shipment Management Workflow", () => { 4 test.beforeEach(async ({ page }) => { 5 // Login and navigate to app 6 await page.goto("/apps/shipping-manager"); 7 await page.waitForLoadState("networkidle"); 8 }); 9 10 test("should create a new shipment", async ({ page }) => { 11 // Click create shipment button 12 await page.click('[data-testid="create-shipment-btn"]'); 13 14 // Fill out shipment form 15 await page.fill('[data-testid="recipient-name"]', "John Doe"); 16 await page.fill('[data-testid="recipient-address"]', "123 Main St"); 17 await page.fill('[data-testid="recipient-city"]', "New York"); 18 await page.selectOption('[data-testid="recipient-state"]', "NY"); 19 await page.fill('[data-testid="recipient-zip"]', "10001"); 20 21 // Fill parcel information 22 await page.fill('[data-testid="parcel-weight"]', "2.5"); 23 await page.fill('[data-testid="parcel-length"]', "12"); 24 await page.fill('[data-testid="parcel-width"]', "8"); 25 await page.fill('[data-testid="parcel-height"]', "6"); 26 27 // Select carrier and service 28 await page.selectOption('[data-testid="carrier-select"]', "fedex"); 29 await page.selectOption('[data-testid="service-select"]', "fedex_ground"); 30 31 // Submit form 32 await page.click('[data-testid="create-shipment-submit"]'); 33 34 // Verify success 35 await expect(page.locator('[data-testid="success-message"]')).toBeVisible(); 36 await expect(page.locator('[data-testid="tracking-number"]')).toBeVisible(); 37 }); 38 39 test("should display shipments list", async ({ page }) => { 40 // Wait for shipments to load 41 await page.waitForSelector('[data-testid="shipments-table"]'); 42 43 // Verify table headers 44 await expect(page.locator('th:has-text("Tracking Number")')).toBeVisible(); 45 await expect(page.locator('th:has-text("Recipient")')).toBeVisible(); 46 await expect(page.locator('th:has-text("Status")')).toBeVisible(); 47 48 // Verify at least one shipment row exists 49 await expect( 50 page.locator('[data-testid="shipment-row"]').first(), 51 ).toBeVisible(); 52 }); 53 54 test("should filter shipments by status", async ({ page }) => { 55 // Apply delivered filter 56 await page.selectOption('[data-testid="status-filter"]', "delivered"); 57 58 // Wait for filtered results 59 await page.waitForTimeout(1000); 60 61 // Verify all visible shipments have delivered status 62 const statusBadges = page.locator('[data-testid="status-badge"]'); 63 const count = await statusBadges.count(); 64 65 for (let i = 0; i < count; i++) { 66 await expect(statusBadges.nth(i)).toHaveText("delivered"); 67 } 68 }); 69});
CI/CD Pipeline
GitHub Actions Workflow
.github/workflows/deploy.yml1name: Deploy Karrio App 2 3on: 4 push: 5 branches: [main, staging] 6 pull_request: 7 branches: [main] 8 9env: 10 NODE_VERSION: "18" 11 REGISTRY: ghcr.io 12 IMAGE_NAME: ${{ github.repository }} 13 14jobs: 15 test: 16 runs-on: ubuntu-latest 17 18 services: 19 postgres: 20 image: postgres:14 21 env: 22 POSTGRES_PASSWORD: postgres 23 POSTGRES_DB: test_db 24 options: >- 25 --health-cmd pg_isready 26 --health-interval 10s 27 --health-timeout 5s 28 --health-retries 5 29 ports: 30 - 5432:5432 31 32 redis: 33 image: redis:7 34 options: >- 35 --health-cmd "redis-cli ping" 36 --health-interval 10s 37 --health-timeout 5s 38 --health-retries 5 39 ports: 40 - 6379:6379 41 42 steps: 43 - name: Checkout code 44 uses: actions/checkout@v4 45 46 - name: Setup Node.js 47 uses: actions/setup-node@v4 48 with: 49 node-version: ${{ env.NODE_VERSION }} 50 cache: "npm" 51 52 - name: Install dependencies 53 run: npm ci 54 55 - name: Run linting 56 run: npm run lint 57 58 - name: Run type checking 59 run: npm run type-check 60 61 - name: Run unit tests 62 run: npm test -- --coverage 63 env: 64 DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db 65 REDIS_URL: redis://localhost:6379 66 67 - name: Run integration tests 68 run: npm run test:integration 69 env: 70 DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db 71 REDIS_URL: redis://localhost:6379 72 73 - name: Install Playwright browsers 74 run: npx playwright install --with-deps 75 76 - name: Run E2E tests 77 run: npm run test:e2e 78 env: 79 DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db 80 81 - name: Upload test results 82 uses: actions/upload-artifact@v4 83 if: failure() 84 with: 85 name: test-results 86 path: | 87 coverage/ 88 test-results/ 89 playwright-report/ 90 91 build: 92 runs-on: ubuntu-latest 93 needs: test 94 if: github.event_name == 'push' 95 96 steps: 97 - name: Checkout code 98 uses: actions/checkout@v4 99 100 - name: Setup Node.js 101 uses: actions/setup-node@v4 102 with: 103 node-version: ${{ env.NODE_VERSION }} 104 cache: "npm" 105 106 - name: Install dependencies 107 run: npm ci 108 109 - name: Build application 110 run: npm run build 111 112 - name: Log in to Container Registry 113 uses: docker/login-action@v3 114 with: 115 registry: ${{ env.REGISTRY }} 116 username: ${{ github.actor }} 117 password: ${{ secrets.GITHUB_TOKEN }} 118 119 - name: Extract metadata 120 id: meta 121 uses: docker/metadata-action@v5 122 with: 123 images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 124 tags: | 125 type=ref,event=branch 126 type=ref,event=pr 127 type=sha 128 129 - name: Build and push Docker image 130 uses: docker/build-push-action@v5 131 with: 132 context: . 133 push: true 134 tags: ${{ steps.meta.outputs.tags }} 135 labels: ${{ steps.meta.outputs.labels }} 136 137 deploy-staging: 138 runs-on: ubuntu-latest 139 needs: build 140 if: github.ref == 'refs/heads/staging' 141 environment: staging 142 143 steps: 144 - name: Deploy to staging 145 run: | 146 echo "Deploying to staging environment..." 147 # Add your staging deployment commands here 148 149 deploy-production: 150 runs-on: ubuntu-latest 151 needs: build 152 if: github.ref == 'refs/heads/main' 153 environment: production 154 155 steps: 156 - name: Deploy to production 157 run: | 158 echo "Deploying to production environment..." 159 # Add your production deployment commands here
Docker Configuration
Dockerfile1FROM node:18-alpine AS base 2 3# Install dependencies only when needed 4FROM base AS deps 5RUN apk add --no-cache libc6-compat 6WORKDIR /app 7 8# Install dependencies based on the preferred package manager 9COPY package.json package-lock.json* ./ 10RUN npm ci --only=production 11 12# Build the app 13FROM base AS builder 14WORKDIR /app 15COPY /app/node_modules ./node_modules 16COPY . . 17 18ENV NEXT_TELEMETRY_DISABLED 1 19 20RUN npm run build 21 22# Production image, copy all the files and run next 23FROM base AS runner 24WORKDIR /app 25 26ENV NODE_ENV production 27ENV NEXT_TELEMETRY_DISABLED 1 28 29RUN addgroup --system --gid 1001 nodejs 30RUN adduser --system --uid 1001 nextjs 31 32COPY /app/public ./public 33 34# Set the correct permission for prerender cache 35RUN mkdir .next 36RUN chown nextjs:nodejs .next 37 38# Automatically leverage output traces to reduce image size 39COPY /app/.next/standalone ./ 40COPY /app/.next/static ./.next/static 41 42USER nextjs 43 44EXPOSE 3000 45 46ENV PORT 3000 47ENV HOSTNAME "0.0.0.0" 48 49CMD ["node", "server.js"]
docker-compose.yml1version: "3.8" 2 3services: 4 app: 5 build: . 6 ports: 7 - "3000:3000" 8 environment: 9 - NODE_ENV=production 10 - DATABASE_URL=${DATABASE_URL} 11 - REDIS_URL=${REDIS_URL} 12 - KARRIO_API_URL=${KARRIO_API_URL} 13 - KARRIO_API_KEY=${KARRIO_API_KEY} 14 depends_on: 15 - db 16 - redis 17 restart: unless-stopped 18 19 db: 20 image: postgres:14 21 environment: 22 - POSTGRES_DB=karrio_app 23 - POSTGRES_USER=karrio 24 - POSTGRES_PASSWORD=${DB_PASSWORD} 25 volumes: 26 - postgres_data:/var/lib/postgresql/data 27 restart: unless-stopped 28 29 redis: 30 image: redis:7-alpine 31 restart: unless-stopped 32 33 nginx: 34 image: nginx:alpine 35 ports: 36 - "80:80" 37 - "443:443" 38 volumes: 39 - ./docker/nginx.conf:/etc/nginx/nginx.conf 40 - ./ssl:/etc/ssl 41 depends_on: 42 - app 43 restart: unless-stopped 44 45volumes: 46 postgres_data:
Staging Environment
Staging Configuration
.env.staging1NODE_ENV=staging 2KARRIO_API_URL=https://staging-api.karrio.io 3KARRIO_API_KEY=staging-api-key 4DATABASE_URL=postgresql://user:pass@staging-db:5432/karrio_app_staging 5REDIS_URL=redis://staging-redis:6379 6JWT_APP_SECRET_KEY=staging-jwt-secret 7ENABLE_ANALYTICS=false 8DEBUG_MODE=true
Staging Deployment Script
1#!/bin/bash 2# scripts/deploy-staging.sh 3 4set -e 5 6echo "π Deploying to staging environment..." 7 8# Pull latest changes 9git checkout staging 10git pull origin staging 11 12# Install dependencies 13npm ci 14 15# Run tests 16npm test 17 18# Build application 19npm run build 20 21# Stop existing containers 22docker-compose -f docker-compose.staging.yml down 23 24# Build and start new containers 25docker-compose -f docker-compose.staging.yml up -d --build 26 27# Wait for services to be ready 28echo "β³ Waiting for services to start..." 29sleep 30 30 31# Run database migrations 32docker-compose -f docker-compose.staging.yml exec app npm run db:migrate 33 34# Run smoke tests 35npm run test:smoke 36 37echo "β Staging deployment completed successfully!" 38echo "π Application available at: https://staging-app.karrio.io"
Production Deployment
Production Configuration
.env.production1NODE_ENV=production 2KARRIO_API_URL=https://api.karrio.io 3KARRIO_API_KEY=production-api-key 4DATABASE_URL=postgresql://user:pass@prod-db:5432/karrio_app 5REDIS_URL=redis://prod-redis:6379 6JWT_APP_SECRET_KEY=production-jwt-secret 7ENABLE_ANALYTICS=true 8DEBUG_MODE=false 9SSL_ENABLED=true 10RATE_LIMIT_ENABLED=true
Blue-Green Deployment
1#!/bin/bash 2# scripts/deploy-production.sh 3 4set -e 5 6BLUE_PORT=3000 7GREEN_PORT=3001 8CURRENT_PORT=$(curl -s http://localhost/health | jq -r '.port' || echo $BLUE_PORT) 9 10if [ "$CURRENT_PORT" = "$BLUE_PORT" ]; then 11 DEPLOY_PORT=$GREEN_PORT 12 DEPLOY_COLOR="green" 13else 14 DEPLOY_PORT=$BLUE_PORT 15 DEPLOY_COLOR="blue" 16fi 17 18echo "π Starting $DEPLOY_COLOR deployment on port $DEPLOY_PORT..." 19 20# Build new image 21docker build -t karrio-app:$DEPLOY_COLOR . 22 23# Start new container 24docker run -d \ 25 --name karrio-app-$DEPLOY_COLOR \ 26 --port $DEPLOY_PORT:3000 \ 27 --env-file .env.production \ 28 karrio-app:$DEPLOY_COLOR 29 30# Health check 31echo "β³ Performing health checks..." 32for i in {1..30}; do 33 if curl -f http://localhost:$DEPLOY_PORT/health; then 34 echo "β Health check passed" 35 break 36 fi 37 38 if [ $i -eq 30 ]; then 39 echo "β Health check failed" 40 docker stop karrio-app-$DEPLOY_COLOR 41 docker rm karrio-app-$DEPLOY_COLOR 42 exit 1 43 fi 44 45 sleep 2 46done 47 48# Switch traffic 49echo "π Switching traffic to $DEPLOY_COLOR..." 50# Update load balancer configuration 51# This would typically update your nginx/HAProxy/AWS ALB configuration 52 53# Stop old container 54if [ "$CURRENT_PORT" = "$BLUE_PORT" ]; then 55 docker stop karrio-app-blue 56 docker rm karrio-app-blue 57else 58 docker stop karrio-app-green 59 docker rm karrio-app-green 60fi 61 62echo "β Production deployment completed successfully!"
Kubernetes Deployment
k8s/deployment.yml1apiVersion: apps/v1 2kind: Deployment 3metadata: 4 name: karrio-app 5 labels: 6 app: karrio-app 7spec: 8 replicas: 3 9 selector: 10 matchLabels: 11 app: karrio-app 12 template: 13 metadata: 14 labels: 15 app: karrio-app 16 spec: 17 containers: 18 - name: karrio-app 19 image: ghcr.io/your-org/karrio-app:latest 20 ports: 21 - containerPort: 3000 22 env: 23 - name: NODE_ENV 24 value: "production" 25 - name: DATABASE_URL 26 valueFrom: 27 secretKeyRef: 28 name: karrio-app-secrets 29 key: database-url 30 - name: KARRIO_API_KEY 31 valueFrom: 32 secretKeyRef: 33 name: karrio-app-secrets 34 key: karrio-api-key 35 resources: 36 requests: 37 memory: "256Mi" 38 cpu: "250m" 39 limits: 40 memory: "512Mi" 41 cpu: "500m" 42 livenessProbe: 43 httpGet: 44 path: /health 45 port: 3000 46 initialDelaySeconds: 30 47 periodSeconds: 10 48 readinessProbe: 49 httpGet: 50 path: /ready 51 port: 3000 52 initialDelaySeconds: 5 53 periodSeconds: 5 54 55--- 56apiVersion: v1 57kind: Service 58metadata: 59 name: karrio-app-service 60spec: 61 selector: 62 app: karrio-app 63 ports: 64 - protocol: TCP 65 port: 80 66 targetPort: 3000 67 type: LoadBalancer 68 69--- 70apiVersion: networking.k8s.io/v1 71kind: Ingress 72metadata: 73 name: karrio-app-ingress 74 annotations: 75 kubernetes.io/ingress.class: nginx 76 cert-manager.io/cluster-issuer: letsencrypt-prod 77spec: 78 tls: 79 - hosts: 80 - your-app.karrio.io 81 secretName: karrio-app-tls 82 rules: 83 - host: your-app.karrio.io 84 http: 85 paths: 86 - path: / 87 pathType: Prefix 88 backend: 89 service: 90 name: karrio-app-service 91 port: 92 number: 80
Monitoring & Observability
Health Checks
pages/api/health.ts1import { NextApiRequest, NextApiResponse } from "next"; 2 3export default async function health( 4 req: NextApiRequest, 5 res: NextApiResponse, 6) { 7 try { 8 // Check database connection 9 await checkDatabase(); 10 11 // Check Redis connection 12 await checkRedis(); 13 14 // Check Karrio API connectivity 15 await checkKarrioAPI(); 16 17 res.status(200).json({ 18 status: "healthy", 19 timestamp: new Date().toISOString(), 20 uptime: process.uptime(), 21 version: process.env.npm_package_version, 22 environment: process.env.NODE_ENV, 23 }); 24 } catch (error) { 25 res.status(503).json({ 26 status: "unhealthy", 27 error: error.message, 28 timestamp: new Date().toISOString(), 29 }); 30 } 31} 32 33async function checkDatabase() { 34 // Implement database health check 35} 36 37async function checkRedis() { 38 // Implement Redis health check 39} 40 41async function checkKarrioAPI() { 42 // Implement Karrio API health check 43}
Application Metrics
lib/metrics.ts1import { createPrometheusMetrics } from "@prometheus/client"; 2 3export const metrics = { 4 httpRequestDuration: new Histogram({ 5 name: "http_request_duration_seconds", 6 help: "Duration of HTTP requests in seconds", 7 labelNames: ["method", "route", "status_code"], 8 }), 9 10 shipmentOperations: new Counter({ 11 name: "shipment_operations_total", 12 help: "Total number of shipment operations", 13 labelNames: ["operation", "status"], 14 }), 15 16 apiErrors: new Counter({ 17 name: "api_errors_total", 18 help: "Total number of API errors", 19 labelNames: ["endpoint", "error_type"], 20 }), 21 22 activeConnections: new Gauge({ 23 name: "active_connections", 24 help: "Number of active connections", 25 }), 26}; 27 28// Middleware to collect metrics 29export function metricsMiddleware(req, res, next) { 30 const start = Date.now(); 31 32 res.on("finish", () => { 33 const duration = (Date.now() - start) / 1000; 34 metrics.httpRequestDuration 35 .labels(req.method, req.route?.path || req.url, res.statusCode) 36 .observe(duration); 37 }); 38 39 next(); 40}
Logging
lib/logger.ts1import winston from "winston"; 2 3const logger = winston.createLogger({ 4 level: process.env.LOG_LEVEL || "info", 5 format: winston.format.combine( 6 winston.format.timestamp(), 7 winston.format.errors({ stack: true }), 8 winston.format.json(), 9 ), 10 defaultMeta: { 11 service: "karrio-app", 12 version: process.env.npm_package_version, 13 }, 14 transports: [ 15 new winston.transports.File({ filename: "logs/error.log", level: "error" }), 16 new winston.transports.File({ filename: "logs/combined.log" }), 17 new winston.transports.Console({ 18 format: winston.format.simple(), 19 }), 20 ], 21}); 22 23export { logger }; 24 25// Usage in your app 26export function logShipmentCreation(shipment) { 27 logger.info("Shipment created", { 28 shipmentId: shipment.id, 29 trackingNumber: shipment.tracking_number, 30 carrier: shipment.carrier.name, 31 userId: shipment.created_by, 32 }); 33} 34 35export function logAPIError(error, context) { 36 logger.error("API Error", { 37 error: error.message, 38 stack: error.stack, 39 context, 40 timestamp: new Date().toISOString(), 41 }); 42}
Environment Management
Environment Variables
.env.example1# Application 2NODE_ENV=development 3PORT=3000 4APP_URL=http://localhost:3000 5 6# Karrio API 7KARRIO_API_URL=https://api.karrio.io 8KARRIO_API_KEY=your-api-key 9 10# Authentication 11JWT_APP_SECRET_KEY=your-jwt-secret 12SESSION_SECRET=your-session-secret 13 14# Database 15DATABASE_URL=postgresql://user:pass@localhost:5432/karrio_app 16 17# Redis 18REDIS_URL=redis://localhost:6379 19 20# External Services 21STRIPE_SECRET_KEY=sk_test_... 22SENDGRID_API_KEY=SG.... 23 24# Monitoring 25SENTRY_DSN=https://... 26NEW_RELIC_LICENSE_KEY=... 27 28# Feature Flags 29ENABLE_ANALYTICS=true 30ENABLE_RATE_LIMITING=true 31DEBUG_MODE=false
Configuration Management
lib/config.ts1interface Config { 2 app: { 3 name: string; 4 version: string; 5 url: string; 6 port: number; 7 }; 8 karrio: { 9 apiUrl: string; 10 apiKey: string; 11 }; 12 database: { 13 url: string; 14 }; 15 redis: { 16 url: string; 17 }; 18 features: { 19 analytics: boolean; 20 rateLimiting: boolean; 21 debugMode: boolean; 22 }; 23} 24 25function validateConfig(): Config { 26 const requiredEnvVars = [ 27 "KARRIO_API_URL", 28 "KARRIO_API_KEY", 29 "DATABASE_URL", 30 "JWT_APP_SECRET_KEY", 31 ]; 32 33 for (const envVar of requiredEnvVars) { 34 if (!process.env[envVar]) { 35 throw new Error(`Missing required environment variable: ${envVar}`); 36 } 37 } 38 39 return { 40 app: { 41 name: process.env.npm_package_name || "karrio-app", 42 version: process.env.npm_package_version || "1.0.0", 43 url: process.env.APP_URL || "http://localhost:3000", 44 port: parseInt(process.env.PORT || "3000"), 45 }, 46 karrio: { 47 apiUrl: process.env.KARRIO_API_URL!, 48 apiKey: process.env.KARRIO_API_KEY!, 49 }, 50 database: { 51 url: process.env.DATABASE_URL!, 52 }, 53 redis: { 54 url: process.env.REDIS_URL || "redis://localhost:6379", 55 }, 56 features: { 57 analytics: process.env.ENABLE_ANALYTICS === "true", 58 rateLimiting: process.env.ENABLE_RATE_LIMITING === "true", 59 debugMode: process.env.DEBUG_MODE === "true", 60 }, 61 }; 62} 63 64export const config = validateConfig();
Best Practices
Security
Security headers middleware1export function securityHeaders(req, res, next) { 2 res.setHeader("X-Content-Type-Options", "nosniff"); 3 res.setHeader("X-Frame-Options", "DENY"); 4 res.setHeader("X-XSS-Protection", "1; mode=block"); 5 res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin"); 6 res.setHeader( 7 "Content-Security-Policy", 8 [ 9 "default-src 'self'", 10 "script-src 'self' 'unsafe-inline'", 11 "style-src 'self' 'unsafe-inline'", 12 "img-src 'self' data: https:", 13 "connect-src 'self' https://api.karrio.io", 14 ].join("; "), 15 ); 16 17 next(); 18} 19 20// Rate limiting 21import rateLimit from "express-rate-limit"; 22 23export const rateLimiter = rateLimit({ 24 windowMs: 15 * 60 * 1000, // 15 minutes 25 max: 100, // limit each IP to 100 requests per windowMs 26 message: "Too many requests from this IP", 27});
Performance
Caching strategy1import NodeCache from "node-cache"; 2 3const cache = new NodeCache({ stdTTL: 600 }); // 10 minutes 4 5export function withCache(key: string, ttl?: number) { 6 return function ( 7 target: any, 8 propertyName: string, 9 descriptor: PropertyDescriptor, 10 ) { 11 const method = descriptor.value; 12 13 descriptor.value = async function (...args: any[]) { 14 const cacheKey = `${key}:${JSON.stringify(args)}`; 15 16 const cached = cache.get(cacheKey); 17 if (cached) { 18 return cached; 19 } 20 21 const result = await method.apply(this, args); 22 cache.set(cacheKey, result, ttl); 23 24 return result; 25 }; 26 }; 27} 28 29// Usage 30class ShipmentService { 31 @withCache("shipments", 300) // 5 minutes 32 async getShipments(filters: any) { 33 // Implementation 34 } 35}
Troubleshooting
Common Issues
-
Database Connection Errors
Check database connectivity1npx prisma db push --preview-feature 2 3# Verify database schema 4npx prisma db pull
-
API Authentication Failures
Verify API key format1echo $KARRIO_API_KEY | base64 -d 2 3# Test API connectivity 4curl -H "Authorization: Token $KARRIO_API_KEY" https://api.karrio.io/v1/carriers
-
Build Failures
Clear build cache1rm -rf .next 2rm -rf node_modules 3npm install 4npm run build
Debugging
Debug utilities1export const debug = { 2 logRequest: (req: NextApiRequest) => { 3 if (process.env.DEBUG_MODE === "true") { 4 console.log("π Request:", { 5 method: req.method, 6 url: req.url, 7 headers: req.headers, 8 body: req.body, 9 }); 10 } 11 }, 12 13 logResponse: (res: any, data: any) => { 14 if (process.env.DEBUG_MODE === "true") { 15 console.log("π€ Response:", { 16 status: res.statusCode, 17 data, 18 }); 19 } 20 }, 21};
Next Steps
- Examples - See complete deployment examples
- Authentication - Review security best practices
- API Integration - Optimize API usage
Deploy your Karrio Apps with confidence using these battle-tested strategies!