Chapter 52: Integration
1. What “Integration” really means in Node.js
When developers say “integration” in a Node.js backend, they almost always mean one of these 7 situations:
- Integrating with external HTTP APIs (Stripe, Twilio, SendGrid, Google APIs, payment gateways…)
- Integrating with databases (PostgreSQL, MongoDB, Redis, DynamoDB…)
- Integrating with message queues / event buses (RabbitMQ, Kafka, Redis Streams, BullMQ, SQS…)
- Integrating with authentication providers (Auth0, Clerk, Supabase Auth, Firebase Auth, Okta…)
- Integrating with file storage (S3, Cloudinary, Supabase Storage, Backblaze…)
- Integrating with monitoring / observability (Sentry, Datadog, New Relic, Prometheus + Grafana…)
- Integrating with frontend (React/Next.js/Vue/Svelte → API calls, WebSockets, Server Components…)
The most frequent real-world question developers ask:
“How do I properly integrate X with my Node.js app so that it is:
- reliable (retries, circuit breakers, timeouts)
- observable (logs, metrics, traces)
- secure (secrets management, least privilege)
- testable (mockable, injectable)
- maintainable (clean separation of concerns)”
This guide focuses on answering exactly that — with concrete code.
2. Core principles of good integration in Node.js (2025–2026)
- Never block the event loop — use async/await or streams
- Always have timeouts & retries — external services fail
- Treat external services as untrusted — validate everything
- Centralize configuration & secrets — env + Zod + secret managers
- Use dependency injection — make services mockable
- Log + trace + monitor every call — you will need it in production
- Fail fast in development, fail gracefully in production
3. Example 1 – Integrating with an external HTTP API (Stripe payments)
Most common integration type
Realistic requirements
- Charge credit card
- Handle webhook events
- Retry on network failure
- Log every request/response
- Type-safe responses
|
0 1 2 3 4 5 6 |
npm install axios @types/axios zod stripe dotenv |
src/config/env.ts
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
import { z } from 'zod' import 'dotenv/config' export const env = z.object({ PORT: z.coerce.number().default(5000), STRIPE_SECRET_KEY: z.string().min(30), STRIPE_WEBHOOK_SECRET: z.string().min(30) }).parse(process.env) |
src/services/payment.service.ts
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
import Stripe from 'stripe' import { env } from '../config/env.js' import { AppError } from '../middleware/error.middleware.js' const stripe = new Stripe(env.STRIPE_SECRET_KEY, { apiVersion: '2024-06-20' // current stable version in 2025–2026 }) export async function createPaymentIntent(amount: number, currency = 'inr') { try { const paymentIntent = await stripe.paymentIntents.create({ amount: amount * 100, // in paise currency, automatic_payment_methods: { enabled: true } }) return { clientSecret: paymentIntent.client_secret, id: paymentIntent.id } } catch (err: any) { console.error('Stripe error:', err) throw new AppError(500, 'Payment initialization failed') } } export async function constructWebhookEvent( rawBody: Buffer, signature: string ) { try { return stripe.webhooks.constructEvent( rawBody, signature, env.STRIPE_WEBHOOK_SECRET ) } catch (err: any) { throw new AppError(400, `Webhook signature verification failed: ${err.message}`) } } |
src/routes/payment.routes.ts
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
import { Router } from 'express' import { z } from 'zod' import { createPaymentIntent } from '../services/payment.service.js' const router = Router() const createIntentSchema = z.object({ amount: z.number().positive().int() }) router.post('/create-intent', async (req, res) => { const { amount } = createIntentSchema.parse(req.body) const intent = await createPaymentIntent(amount) res.json(intent) }) // Webhook – important: raw body needed router.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => { const sig = req.headers['stripe-signature'] as string try { const event = await constructWebhookEvent(req.body, sig) switch (event.type) { case 'payment_intent.succeeded': console.log('Payment succeeded:', event.data.object.id) // update order status, send email, etc. break // handle other events... default: console.log(`Unhandled event type ${event.type}`) } res.json({ received: true }) } catch (err: any) { res.status(400).send(`Webhook Error: ${err.message}`) } }) export default router |
4. Example 2 – Integrating with PostgreSQL (Prisma style – most popular in 2025–2026)
Install Prisma
|
0 1 2 3 4 5 6 7 |
npm install @prisma/client npm install -D prisma |
prisma/schema.prisma
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model User { id String @id @default(uuid()) email String @unique name String? createdAt DateTime @default(now()) tasks Task[] } model Task { id String @id @default(uuid()) title String description String? completed Boolean @default(false) userId String user User @relation(fields: [userId], references: [id]) } |
Generate & migrate
|
0 1 2 3 4 5 6 7 |
npx prisma generate npx prisma migrate dev --name init |
src/services/task.service.ts
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import { prisma } from '../prisma/client.js' import { CreateTaskInput } from '../schemas/task.schema.js' export async function createTask(input: CreateTaskInput, userId: string) { return prisma.task.create({ data: { ...input, userId } }) } export async function getUserTasks(userId: string) { return prisma.task.findMany({ where: { userId }, orderBy: { createdAt: 'desc' } }) } |
src/controllers/task.controller.ts
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import { Request, Response } from 'express' import { createTaskSchema } from '../schemas/task.schema.js' import * as taskService from '../services/task.service.js' export const createTask = async (req: Request, res: Response) => { const input = createTaskSchema.parse(req.body) const userId = req.user!.userId // from auth middleware const task = await taskService.createTask(input, userId) res.status(201).json(task) } export const getMyTasks = async (req: Request, res: Response) => { const tasks = await taskService.getUserTasks(req.user!.userId) res.json(tasks) } |
Summary – Modern Node.js integration patterns (2025–2026)
| Integration type | Most popular stack right now | Typical latency expectation | Retry strategy | Observability must-have |
|---|---|---|---|---|
| External HTTP API | axios / undici / fetch + p-retry | 100–800 ms | Exponential backoff | Request ID, latency histogram |
| PostgreSQL | Prisma / Drizzle / Kysely | 5–50 ms | Built-in | Query logging, slow query alert |
| MongoDB | Mongoose / native driver | 5–100 ms | Built-in | Slow query + connection pool |
| Redis / Key-value | ioredis / redis | <5 ms | Built-in | Cache hit/miss ratio |
| Message queue | BullMQ / RabbitMQ / KafkaJS | 10 ms – 1 s | Dead letter | Queue depth, processing time |
| Authentication provider | Auth0 / Clerk / Supabase Auth | 100–500 ms | — | Failed login rate |
Which integration would you like to go much deeper into next?
- Full Stripe payments integration (webhooks, retries, idempotency)
- Prisma + Zod + pagination + filtering complete example
- Drizzle ORM + raw SQL + type safety
- BullMQ / Redis background jobs with retries & priorities
- Supabase auth + PostgreSQL + storage in one project
- Observability (logging + tracing + metrics) across integrations
Just tell me which direction you want — I’ll continue with complete, production-ready code and explanations. 😊
