Every serious production app relies on services it didn't build. Instead of writing your own payment processor, you integrate Stripe. Instead of building email infrastructure, you use Resend. This lesson covers the patterns that connect your app to those external services, patterns you'll use constantly in your career.
Why use third-party services
Building payments, authenticationWhat is authentication?Verifying who a user is, typically through credentials like a password or token., or email delivery from scratch is a trap. Each one looks simple on the surface and hides enormous complexity underneath, PCI compliance, deliverability, security audits, edge cases. Third-party services solve that complexity so you can focus on what your app actually does.
Think of it like plumbing. You don't install your own water treatment plant when you build a house, you connect to the municipal system. Third-party APIs are the municipal systems of the software world.
| Service category | Popular options | What it saves you |
|---|---|---|
| Payments | Stripe, Paddle, Braintree | PCI compliance, fraud detection, chargeback handling |
| Authentication | Clerk, Auth0, Firebase Auth | Password hashing, 2FA, session management, SSO |
| Resend, SendGrid, Postmark | Deliverability, DNS setup, bounce handling | |
| Storage | Cloudflare R2, AWS S3 | Infrastructure, redundancy, CDN |
| Monitoring | Sentry, Datadog | Error tracking, alerting, dashboards |
AuthenticationWhat is authentication?Verifying who a user is, typically through credentials like a password or token. methods
APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses. keys
The simplest and most common approach. You get a secret string from the providerWhat is provider?A wrapper component that makes data available to all components nested inside it without passing props manually.'s dashboard, and you include it with every request. Most SDKs handle this automatically once you initialize with your key.
// The SDK handles auth headers for you
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
// Under the hood it's just adding an Authorization header
// Authorization: Bearer sk_live_....env locally, secrets manager in production). A leaked key can cost you thousands of dollars in fraudulent API calls before you notice.OAuth 2.0What is oauth?An authorization protocol that lets users grant a third-party app limited access to their account on another service without sharing their password.
OAuth is used when your app needs to act on behalf of a user, for example, posting to their Twitter account or reading their Google Calendar. The flow has three steps: redirect the user to the provider, they authorize your app, the provider gives you a tokenWhat is token?The smallest unit of text an LLM processes - roughly three-quarters of a word. API pricing is based on how many tokens you use..
// Step 1: Redirect user to the provider's authorization page
const authUrl = `https://accounts.google.com/o/oauth2/auth?
client_id=${CLIENT_ID}
&redirect_uri=${REDIRECT_URI}
&scope=email profile
&response_type=code`;
// Step 2: Provider redirects back with a code
// GET /callback?code=abc123
// Step 3: Exchange the code for an access token
const tokenResponse = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
body: JSON.stringify({
code: req.query.code,
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
redirect_uri: REDIRECT_URI,
grant_type: 'authorization_code'
})
});
const { access_token } = await tokenResponse.json();Webhooks
Webhooks are the inverse of a regular API call. Instead of you asking "did anything happen?", the service pushes an HTTPWhat is http?The protocol browsers and servers use to exchange web pages, API data, and other resources, defining how requests and responses are formatted. POST to your server the moment something happens. Think of it like signing up for text alerts instead of refreshing a web page repeatedly.
// Your endpoint that Stripe (or any service) will POST to
app.post('/webhooks/stripe', express.raw({ type: 'application/json' }), (req, res) => {
const event = JSON.parse(req.body);
switch (event.type) {
case 'payment_intent.succeeded':
console.log('Payment succeeded:', event.data.object.id);
// Provision the user's subscription
break;
case 'customer.subscription.deleted':
console.log('Subscription cancelled');
// Revoke access
break;
}
// Always respond quickly - the service will retry if you don't
res.json({ received: true });
});SDKs vs raw RESTWhat is rest?An architectural style for web APIs where URLs represent resources (nouns) and HTTP methods (GET, POST, PUT, DELETE) represent actions on those resources. calls
Most major services offer an official SDKWhat is sdk?A pre-built library from a service provider that wraps their API into convenient functions you call in your code instead of writing raw HTTP requests. (a library you install via npm). You can also always call their REST APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses. directly with fetch. Both work, and the right choice depends on your situation.
| Approach | Pros | Cons |
|---|---|---|
| SDK | Less boilerplate, handles auth and serialization, TypeScript types included | Adds a dependency, may lag behind the API |
| Raw REST | No dependency, full control, works anywhere | More code, you handle auth headers and error parsing manually |
For most projects, start with the SDK. Switch to raw REST only if the SDK is missing a feature you need or you want zero dependencies.
Rate limitingWhat is rate limiting?Restricting how many requests a client can make within a time window. Prevents brute-force attacks and protects your API from being overwhelmed.
Every APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses. providerWhat is provider?A wrapper component that makes data available to all components nested inside it without passing props manually. caps how many requests you can make per second or per day. Exceed the limit and you get a 429 Too Many Requests response. Your code must handle this, a crash loop against a rate-limited API is a bad day.
async function callWithRetry(fn, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
const isRateLimit = error.status === 429 || error.code === 'rate_limit';
if (isRateLimit && attempt < maxRetries - 1) {
// Exponential backoff: wait longer each time
const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
console.log(`Rate limited. Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
throw error; // Give up after max retries
}
}
}
// Usage
const result = await callWithRetry(() => stripe.customers.list({ limit: 100 }));Quick reference
| Auth type | When to use | Example |
|---|---|---|
| API key | Server-to-server calls | Stripe, Resend, OpenAI |
| OAuth 2.0 | Acting on behalf of a user | Google, GitHub, Twitter integrations |
| Webhook | Receiving real-time events | Payment confirmations, email bounces |
| JWT | Stateless session verification | Clerk, Auth0 tokens |
// Secure API call pattern
class APIClient {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseURL = 'https://api.service.com';
}
async request(endpoint, options = {}) {
const response = await fetch(`${this.baseURL}${endpoint}`, {
...options,
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
...options.headers
}
});
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
return response.json();
}
}