API Integration Tutorial for Backend Developers
Learn four decisions—authentication, requests, errors.

API integrations don't usually fail because someone picked the wrong tool. They fail because four decisions, made in sequence, got made wrong: authentication, request handling, error management, and testing. This piece walks through that sequence in order, with the actual patterns that stop each failure mode before it reaches production.
The stakes aren't theoretical. APIs carry most of today's web traffic, and recent data from adalo.com put the share of organizations reporting an API security incident at 95%. Building like it's still 2015 is how you end up in that 95%.
This is written for backend developers building or consuming APIs in real systems, not toy projects. It assumes working knowledge of HTTP and at least one server-side language. It won't benchmark every gateway on the market or rank vendors. It follows one sequence: pick the integration style, lock the contract, wire up auth correctly, harden request handling, design errors that can actually be diagnosed, and choose a gateway that enforces all of it centrally.
Pick Your Integration Style First
Three styles dominate production systems in 2025: REST, GraphQL, and gRPC. Each solves a different problem, and each breaks in its own way when forced into the wrong job. The actual mistake teams make is picking one style and routing everything through it, treating "choosing an integration style" as a single decision instead of three separate ones made for three separate audiences.
REST is stateless. Every request carries everything the server needs to answer it, which is why it fits public APIs and CRUD-heavy systems so well. Browsers, proxies, and CDNs already know how to cache REST, especially with URI versioning. The weakness shows up once your data model grows complex: clients either pull back far more than they need, or they make five round trips just to assemble one screen. That's the over-fetching and under-fetching problem, and it worsens with scale.
GraphQL flips the arrangement around. The client asks for exactly the fields it wants, which eliminates over-fetching. The cost moves to the server instead of disappearing: parsing the query and checking it against the schema takes work, and a deeply nested query can trigger a fan-out of database calls if the resolvers aren't tuned. GraphQL earns its keep when frontend and backend teams iterate together and the shape of the data keeps shifting. Fewer round trips usually pays for the extra server-side cost.
gRPC uses Protocol Buffers, a binary format that serializes roughly three times faster than JSON and produces messages that run 30 to 35% smaller. Pair that with HTTP/2 multiplexing and connection overhead drops significantly, which matters when one microservice calls another thousands of times a second. gRPC also supports four communication patterns (unary, server streaming, client streaming, bidirectional streaming), and the streaming patterns matter for anything real-time. Browser support is thin, so gRPC has no business sitting on a public-facing API.
Netflix runs gRPC internally for video streaming data, GraphQL for recommendations, and REST for account management, each chosen because it fit the job. A practical default for backend teams looks similar: gRPC between internal services, REST for external partners, GraphQL wherever the frontend drives how the data gets shaped. Getting this wrong means changing a wire protocol mid-project, which requires rewriting clients, running two versions in parallel, and migrating schemas under a deadline nobody planned for.
Define the API Contract Before Auth
Name endpoints like nouns, not verbs. /orders, not /getOrders. The URL names the resource; the HTTP method (GET, POST, DELETE) says what you're doing to it. Do this consistently and the API becomes self-documenting and cacheable, which is exactly how CDNs and proxies already expect things to behave.
Version from the first commit, not after the first time you break something. URI path versioning, /v1/, is the easiest place to start: it's visible, it's cache-friendly, and many large platforms have run on it for years. Header-based versioning works too, but it demands careful cache configuration, or you'll serve a stale response from the wrong version without ever knowing it. The approach worth copying: small, additive, non-breaking changes (a new optional field, a new endpoint) roll out continuously, while anything that actually breaks compatibility waits for a full version bump. That gives consumers room to adapt instead of freezing the API in place forever. Build a deprecation calendar before anyone asks for one, announcing changes well ahead of time and supporting migrations long enough for consumers to adapt before old versions are removed.
Standardize your error shape at the contract level, before anyone builds against it. RFC 7807, "Problem Details for HTTP APIs," is a sound baseline: every error carries a standard HTTP status code, a machine-readable type, and a human-readable detail field. Lock this down early, because once client applications start writing error-handling logic around your schema, changing that schema becomes a breaking change, whether you intended it to be one or not.
Generate documentation straight from the OpenAPI or Protobuf spec, so the docs can't drift from what the API actually does. This reduces support tickets, speeds up partner integrations, and gets new engineers productive faster than a wiki page that hasn't been updated in months.
Configure Authentication to Close Security Gaps
The OWASP API Security Top 10, current as of 2023 and still the reference point in 2025, names two risks that cause more damage than everything else on the list combined. API1 is Broken Object Level Authorization, or BOLA: an attacker swaps an ID in the request (order 1001 becomes order 1002) and pulls someone else's data, because the server never checked whether the requester actually owns that object. API2 is Broken Authentication: weak tokens, no multi-factor requirement, credentials sitting in a URL where any proxy log can capture them. Neither of these is an unsolved problem, and the fixes have been documented for years.
Most breaches trace back to a missing ownership check that a middleware function would have caught, not to some novel attack nobody saw coming.
OAuth 2.0, and increasingly OAuth 2.1, is the standard for delegated authorization, and the IETF's RFC 9700, published in January 2025, is the current best-practice reference. It specifies what "secure" means in practice:
Use PKCE on every authorization code flow to strengthen the security of the authorization handshake.
Scope token permissions as narrowly as possible, so a stolen token isn't automatically a fully usable one.
Never put a token in a URL query parameter. Logs retain it, proxies retain it, and browser history retains it too.
Treat refresh tokens carefully: if a token that should be expired is presented again, treat that as a signal something's wrong and invalidate the session.
JWTs introduce several common implementation errors. Always validate the alg header on the server side; the "none" algorithm attack and algorithm-confusion attacks both work by tricking a library into trusting whatever algorithm the client claims. Keep access tokens short-lived, minutes rather than hours, and use refresh tokens for anything that needs to persist a session. Store JWTs in a way that limits exposure to script-based attacks. Validate all standard claims on every request, not just at login.
Role-based access control answers one question: what can this type of user do. It never answers whether this specific user owns this specific resource, which is the actual question behind BOLA. That check has to run on every read and write, and it belongs in middleware rather than copy-pasted into every handler, where it will eventually get skipped in the handler that matters most.
API attacks rose 680% between 2021 and 2025, and the average data breach now costs $4.88 million. Skipping the object-level ownership check is a risk that compounds every year.
Harden Request Handling for Real Networks
Correct auth doesn't protect you from a slow dependency, a cascading timeout, a retry storm, or a rate limit exceeded without notice. Those are separate problems that require separate fixes. Teams that treat them as one problem end up debugging the wrong layer while the actual failure continues unaddressed.
Set timeouts explicitly, everywhere. Connect timeout, read timeout, and total request timeout are three different values, and most HTTP client libraries default to none of them being set. Without explicit timeouts, one slow downstream service will exhaust every thread in your pool, one connection at a time, until the whole service stops responding.
Retries need exponential backoff with jitter, and they need firm boundaries. Only retry idempotent operations: GET, PUT, DELETE. Never retry a POST unless an idempotency key backs it up. Exponential backoff means each retry waits twice as long as the previous one; jitter means you randomize that wait slightly, so a large number of clients that failed simultaneously don't all retry simultaneously. Cap the retry count and cap the total elapsed time, or a broken dependency produces a retry loop that never resolves and continuously burns threads.
Idempotency keys solve the problem retries alone can't address. The client generates a unique key for each logical operation, the server stores the result under that key, and if the same request arrives twice, the server returns the cached response instead of executing the operation again. This is the difference between a flaky network connection and a customer being charged twice for one order.
Rate limiting and circuit breakers round out the defense. Enforce limits on inbound traffic so your API doesn't get overwhelmed, and respect limits on outbound traffic so you don't get your account suspended by a provider you depend on. A circuit breaker trips after a threshold of failures and stops sending traffic to a dependency, giving it time to recover rather than receiving continued load. The half-open state periodically tests whether the dependency has recovered before restoring full traffic.
Cap page sizes server-side rather than trusting client-supplied limits, and validate every payload before it reaches deserialization. Malformed input that reaches business logic is a vulnerability on its own, even when the authentication layer is functioning correctly.
Design Errors That Are Actually Diagnosable
Handling an error and communicating it are two different jobs, and most systems only do the first one. Catching an exception and swallowing it silently keeps the service running while the failure becomes invisible to whoever is on call and to whoever is calling your API.
Match your error codes to where the fault lies. A 4xx means the caller did something wrong, so provide enough detail for them to fix it without opening a support ticket. A 5xx means the fault is on your side: log the internal detail privately and return a sanitized message externally, without stack traces or internal file paths. Never return a 200 OK with an error tucked inside the response body, because that breaks any client that trusts the status code and breaks every monitoring tool watching for 5xx spikes.
RFC 7807 provides a structure that works for both machines and humans: type (a URI identifying the error class), title (a short summary), status (the HTTP code), detail (what actually went wrong in this instance), and instance (a URI pointing to the specific failed request). A client branches its logic on type, while a developer debugging the issue reads detail. One schema serves both purposes without requiring a translation layer.
Correlation IDs are among the most underused tools available. Attach a unique ID to every request at the entry point (gateway or load balancer), pass it through every service that handles the request, log it at each hop, and return it in the error response. With that ID, tracing a failure through multiple microservices becomes a log search rather than multiple engineers examining separate dashboards and debating which service is responsible.
Keep certain information out of every error response: internal service names, database table names, file paths, and stack traces. Each one provides an attacker with a piece of your internal architecture. On authentication failures specifically, don't distinguish between "account locked" and "credentials invalid" unless your security model explicitly requires that distinction. Otherwise use a single generic message, because the more specific version tells an attacker which accounts exist and which don't.
Match log severity to what actually warrants attention. A 4xx from an external caller is INFO or WARN, not ERROR. Logging every bad request at ERROR level drowns real alerts in noise. A 5xx or an unhandled exception is ERROR, because that's what should trigger an alert. Log in structured JSON rather than free-text, since log aggregation tools can index a field reliably but can't reliably parse unstructured text.
Choose a Gateway That Enforces Your Contract
A gateway gives you one place to enforce auth, rate limiting, routing, and logging rather than duplicating that logic across every service. It also decouples your callers from your internal architecture: you can swap out a backend service, rescale it, or rewrite it in a different language without touching the contract your clients depend on.
For a backend developer specifically, this is the primary value of a gateway. The auth checks described earlier (object-level authorization, token validation, rate limits) stop being conventions that depend on every engineer remembering to apply them in every handler. They become enforced centrally, in one place that all traffic passes through.
Kong is one of the open-source options in this space, alongside several others built for different scales and different cost models. The right choice depends on request volume, latency requirements, and how much operational overhead the team can realistically absorb. Whichever gateway ends up in front of the API, the evaluation criteria stay the same: does it enforce the contract on every request, or does it only observe and log traffic after the fact.


