Skip to main content
Testing & Quality · Blog 34

Next.js 16 Route Handlers and API Testing

Test App Router APIs at the right depth—from pure validation with Vitest to request boundaries, isolated data, security rules and real HTTP flows with Playwright.

Route Handler gateway connecting a browser request to validation, authentication, a database and a successful JSON response

In Blog #33, we built a testing strategy for Server Components, Client Components and Server Actions. The next boundary is the HTTP layer itself. Route Handlers receive requests from browsers, mobile clients, webhooks and external services, so they need tests for status codes, validation, authentication, authorization, data behavior and failure cases.

Next.js API testing should answer more than “does this function run?” A useful suite proves that an endpoint accepts the intended method and input, returns a stable response contract, protects identity and ownership boundaries, changes the correct state, and fails without leaking sensitive internals.

How Do You Test Next.js Route Handlers?

API Testing at a Glance

Use the smallest test that provides credible evidence. Pure functions need no HTTP object. A handler contract needs a Request and Response. Critical routing, cookies and deployment behavior need a running application. Combining these layers produces faster feedback without pretending a mock recreates the entire framework.

Three-level API testing lab representing pure logic, route integration and real HTTP verification
Three levels, three kinds of evidence. Focused tests diagnose rules quickly; integration and real HTTP checks prove progressively wider boundaries.
Diagram 1: API testing layersTest logic and HTTP behavior at different levels instead of forcing everything into one test type.
Pure logicVitest: schema, normalization, business rules
Route boundaryIntegration: Request, handler, Response, controlled service
Real HTTP flowPlaywright: router, server, cookies and deployed contract
Test typePurposeExample
UnitPure validation and business rulesNormalize an email or validate a payload
IntegrationRoute plus service or database boundaryCall an exported POST with a controlled repository
API E2EReal HTTP endpointPlaywright request to the running server
Browser E2EUser flow that invokes the endpointForm → API → success or error UI

What Is a Route Handler?

In the App Router, a Route Handler is a route.ts or route.js file inside app. It uses the Web Request and Response APIs and may opt into Next.js helpers through NextRequest and NextResponse. Current documentation lists GET, POST, PUT, PATCH, DELETE, HEAD and OPTIONS; unsupported methods receive 405.

Educational App Router route
app/api/example/route.ts

export async function GET() {
  return Response.json({ ok: true })
}

export async function POST(request: Request) {
  const body = await request.json()
  return Response.json({ received: body }, { status: 201 })
}
Diagram 2: Request flowA robust route makes each trust transition deliberate.
HTTP Request
Validate
Authenticate
Authorize
Business logic
HTTP Response

Route Handlers vs Server Actions vs pages/api

Blog #10 explains Route Handler implementation, while Blog #7 covers Server Actions. Route Handlers are explicit HTTP endpoints and can serve external clients. Server Actions are designed primarily for mutations from a Next.js UI. The older pages/api convention belongs to the Pages Router; do not paste its request and response types into an App Router test.

ConcernRoute HandlerServer Action
HTTP endpointYesNot primarily
External clientsYesUsually no
Browser form mutationPossibleExcellent fit
Explicit HTTP statusYesDifferent response model
WebhooksYesNo
Internal UI mutationPossibleOften preferable

Route Handler Anatomy and Contract

Treat the method, path, accepted input, authentication expectation, status codes, headers and response schema as one contract. Tests should protect that contract rather than freeze private helper calls. Standard Request is enough for many handlers; use NextRequest when the route needs helpers such as nextUrl or request cookies.

Diagram 3: API contractThe contract connects an address and method to validated input and a predictable response.
AddressMethod + path
InputBody + query + headers
BehaviorRules + side effects
OutputStatus + schema

Unit Testing API Logic with Vitest

Pure logic should not require a fabricated request. Test normalization, calculations, permission decisions and schemas as plain functions. This keeps failures narrow and fast.

normalize-email.test.ts
import { describe, expect, it } from 'vitest'
import { normalizeEmail } from './normalize-email'

describe('normalizeEmail', () => {
  it('trims and lowercases an address', () => {
    expect(normalizeEmail(' Ada@Example.COM '))
      .toBe('ada@example.com')
  })
})

If the application uses Zod or another schema library, import the production schema. Blog #18 explains shared form and validation boundaries. Cover a valid payload, each required field, invalid types, formats and relevant boundaries. A test-only copy of the schema can drift while every test remains green.

Diagram 4: Validation flowThe project chooses its 400 or 422 convention; the suite protects that documented choice.
Request body → production schema
ValidContinue to authorized business logic
InvalidReturn safe 400 or 422 response

Integration Testing Route Handlers Directly

When the handler and installed runtime support it, construct a Web Request and invoke the exported method. This verifies parsing, status and body mapping while keeping the server out of the loop. It does not prove filesystem routing, middleware, deployment headers or the complete Next.js HTTP runtime.

Generic direct handler test
import { expect, it } from 'vitest'
import { POST } from './route'

it('returns a created response', async () => {
  const request = new Request('http://localhost/api/items', {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ name: 'Example' }),
  })

  const response = await POST(request)
  expect(response.status).toBe(201)
  await expect(response.json()).resolves.toMatchObject({ ok: true })
})
Diagram 5: Direct handler vs real HTTPBoth add value, but only the HTTP path includes the router and running server.
Direct testTest → POST(request) → Response
Real HTTPTest → HTTP → Next.js router → handler
Coverage decisionUse both for critical contracts

Testing GET, POST, PUT, PATCH and DELETE

GET Route Handlers

Cover success, empty collections, query parsing, missing resources and the controlled failures relevant to the implementation. A GET endpoint should not mutate application state. Current Next.js documentation says Route Handlers are not cached by default; GET can opt into caching, and Cache Components introduce additional documented behavior. Test the visible freshness contract instead of assuming old defaults.

POST Route Handlers and request bodies

Test valid JSON, malformed JSON, missing fields, domain validation, authentication, conflict behavior and service failure where those branches genuinely exist. request.json() can throw for malformed JSON, so the handler must deliberately translate that failure if its contract promises a clean client error.

PUT and PATCH

PUT conventionally represents replacement while PATCH represents partial change, but the application contract decides. For PATCH, prove omitted fields remain unchanged. For PUT, prove required replacement fields are enforced if that is the selected design.

DELETE

Never test destructive methods against production. Create a record owned by the test, delete it, verify the HTTP result and database state, then clean up safely when necessary.

Diagram 6: Safe mutation testA mutation assertion includes persisted state, not only a successful response.
Arrange owned record
POST / PATCH / DELETE
Assert response
Assert test DB
Rollback / cleanup

Query Parameters, Dynamic Params, Headers and Cookies

For a query such as /api/products?search=keyboard&page=2, cover expected, missing, malformed and out-of-range values. Use new URL(request.url).searchParams with a standard Request, or request.nextUrl.searchParams when the route intentionally accepts NextRequest.

Dynamic parameters are version-sensitive. Current Next.js Route Handler documentation shows an async context and awaits ctx.params, with the typed RouteContext<'/users/[id]'> helper available after type generation. Verify this against the installed package rather than copying an older synchronous example.

Diagram 7: Dynamic route lookupValidate the dynamic ID before using it and distinguish a missing resource from a malformed request.
/api/users/123
Await [id]
Validate ID
Find user
200 or 404

Assert headers that belong to the public contract: content type, deliberate cache policy, CORS controls and required custom headers. Do not snapshot every framework-generated header. For cookies, test absent, valid, updated and cleared states that actually exist, and never print session values into test output or traces.

Status Codes, JSON and Safe Errors

StatusConventional meaningUse only when
200Successful responseThe operation completed normally
201CreatedA new resource was created
204No contentSuccess intentionally has no body
400Bad requestInput cannot be parsed or accepted by contract
401UnauthenticatedAcceptable credentials are absent
403ForbiddenIdentity exists but permission does not
404Not foundThe addressed resource is unavailable
409ConflictA real state or uniqueness conflict exists
422Unprocessable contentThe project adopts it for semantic validation
429Too many requestsA rate limiter actually exists
500Server errorAn unexpected internal failure is safely translated

Projects can choose different conventions; consistency and documentation matter more than forcing every code into every API. Prefer focused JSON assertions to giant snapshots. Verify stable fields and types, while allowing unrelated metadata to evolve.

Diagram 8: Safe error flowDetailed diagnostics stay in secured server logs while the client receives a neutral contract.
Internal error
Secure server logDiagnostic context, redacted values
Safe API responseGeneric message, stable status, no secrets

Testing Authentication and Authorization

Blog #13 covers authentication architecture. At the route boundary, test no credential, a valid test identity and an invalid or expired credential only when the system can reproduce it safely. Then test authorization separately: an authenticated identity is not automatically allowed to access every object.

Diagram 9: 401 vs 403Authentication establishes identity; authorization decides whether that identity may continue.
Request → logged in?
No → 401No acceptable identity
Yes → allowed?No: 403 · Yes: continue

IDOR/BOLA defenses deserve an explicit ownership case: create a resource for test user A, authenticate as test user B, request A's protected resource, expect denial and prove the resource did not change. Do not weaken the test by mocking the permission decision to “allowed.”

Diagram 10: Ownership authorizationChanging an object ID must not bypass the resource-level permission check.
User A owns Resource A
User B requests A
Ownership policy
Denied + unchanged

Database Reads, Writes and Transactions

The database chapter will go deeper in Blog #36, which is not yet published. For Route Handler integration, use a disposable database, dedicated test schema, container or transaction strategy supported by the real stack. Seed a record before GET and assert the API representation. For POST, assert the response and query the test database to verify the correct row.

Diagram 11: API plus database integrationImportant mutations need evidence at both the HTTP and persistence boundaries.
Test
Route Handler
Service / repository
Test database
Response + state

If production code uses a transaction, test meaningful all-or-nothing behavior: force a controlled second-step failure and prove the first write did not remain. Never introduce transaction claims for a stack that does not use them. Cleanup must target only data owned by the test, and mutation suites should fail closed when configuration resembles production.

External APIs, Mocking and Webhooks

Do not call an uncontrolled third party in every test. Put outbound work behind a thin service interface where that improves design, use vi.stubGlobal('fetch', vi.fn()) or the project-standard mock for focused cases, and restore globals after each test. Selected E2E checks can use a provider sandbox or controlled substitute. Blog #37 will cover this boundary in depth and remains intentionally non-clickable.

Diagram 12: External service boundaryMock the owned wrapper for fast tests and use a real sandbox only for selected end-to-end evidence.
Route Handler → service wrapper
Focused testsControlled mock, timeout and error cases
Selected E2EProvider sandbox or controlled substitute

Webhook tests should use the provider's official signing tools with a test secret. Cover valid events, missing or invalid signatures, malformed bodies and unknown event types. Test duplicate delivery only when the system implements idempotency. Never disclose the secret or invent custom cryptography.

Diagram 13: Signed webhook flowSignature verification happens before event parsing and side effects.
Provider
Signed request
Verify signature
Parse event
Process safely

Playwright API Testing

Blog #32 establishes Playwright configuration. Its request fixture supplies an APIRequestContext that can call a server without loading a page. Use the project's safe baseURL and a non-production environment.

Generic Playwright API test
import { expect, test } from '@playwright/test'

test('GET API returns its public contract', async ({ request }) => {
  const response = await request.get('/api/example')

  expect(response.ok()).toBeTruthy()
  expect(response.headers()['content-type'])
    .toContain('application/json')
  await expect(response.json()).resolves.toMatchObject({ ok: true })
})
Diagram 14: Playwright API requestAPIRequestContext exercises the real network route without rendering a browser page.
Playwright
APIRequestContext
HTTP
Next.js server
Route response
GoalAPI testBrowser E2E
Status and bodyExcellentIndirect
Fast endpoint verificationExcellentSlower
User experienceNoExcellent
Browser behaviorNoYes
Auth cookie flowGoodExcellent
Full UI integrationNoYes
Diagram 15: Browser-to-API flowA browser journey proves that the user interface interprets the API contract correctly.
Browser form
fetch('/api/contact')
Route Handler
Response
Success / error UI

API Security Testing

Untrusted API request passing through validation, identity, authorization and business rule checkpoints before reaching a database
Defense is a sequence of independent decisions. Valid input does not imply an authenticated identity, and authentication does not imply object-level permission.

Defensive cases include malformed JSON, missing fields, invalid types, missing authentication, wrong ownership, oversized input where a limit exists, safe error bodies and disallowed methods. For cookie-authenticated mutations, apply the architecture-specific CSRF guidance from Blog #27. If an endpoint accepts external URLs, verify the allowlist and outbound service boundary described in Blog #28. Dependency hygiene connects to Blog #30.

Diagram 16: API security boundaryEvery untrusted request passes validation, identity, permission and business rules before protected resources.
Untrusted request
Validation
Authentication
Authorization
Business rules
Safe response

Rate Limits, CORS, Caching and Revalidation

Test CORS and OPTIONS only when the API intentionally supports cross-origin clients. Avoid a permissive wildcard on private credentialed endpoints. If rate limiting exists, use a deterministic test adapter to cover an allowed request, the actual threshold, 429 and reset behavior without flooding shared infrastructure.

Diagram 17: Rate-limit decisionOnly APIs with an implemented limiter should promise or test a 429 contract.
Requests → rate limiter
Within limitContinue with the endpoint's normal status
Limit exceeded429 and documented retry behavior

For caching and revalidation, verify behavior against the installed Next.js version. A mutation that calls revalidatePath or revalidateTag should ultimately make fresh data observable. A mocked function call alone proves only that a collaborator was invoked.

Test Data, Isolation and Time

Never copy customer data into fixtures. Use generated emails, fake UUIDs, factories and seeded records. Each test should own its records instead of relying on a previous case. Generate unique values for parallel workers and clean only scoped data. For timestamp or expiry rules, control time in focused tests rather than adding fixed sleeps.

Owned dataEvery case creates or receives its own fixture.
Safe environmentA guard refuses production database or service configuration.
Deterministic cleanupRollback, scoped deletion or disposable infrastructure.

Project-Specific Test Matrix

Because this publishing repository contains no Next.js Route Handlers, the truthful matrix is empty. In the real application, inventory three to six safe targets: a public GET, validated mutation, protected resource, database-backed path, expected error and one real HTTP check. Never replace missing architecture with fictional routes.

Detected endpointClassificationTest status
No app/**/route.ts or route.js foundNot applicable in this PHP publishing repositoryNo runtime API tests created or claimed

A Practical GET Route Handler Test Plan

Begin by writing down the GET contract before choosing mocks. Identify whether the route returns a collection, a single resource, a computed view or a proxy response. Record the successful shape, the meaning of an empty result, accepted search parameters, visibility rules and any deliberate cache policy. This prevents a test from quietly deciding behavior that the API never promised.

For a public collection, a compact suite often covers a normal result, an empty array, supported filtering, malformed pagination and a controlled repository failure. If the endpoint returns pagination metadata, assert the fields that clients depend on rather than snapshotting the entire payload. If sort order is contractual, seed records whose order is unambiguous and assert the identifiers in order. If order is not contractual, avoid making it one accidentally.

A single-resource GET usually needs valid-found, valid-missing and malformed-identifier cases. An authenticated route adds missing identity and disallowed ownership. Keep these cases independent: a 404 assertion should not rely on another test deleting the record first. Whether a protected API returns 403 or intentionally hides existence with 404 is an application security decision; document it and test it consistently.

Service failure cases should begin at a boundary the test controls. Configure the repository or service substitute to throw a recognized error and confirm the route maps it to a safe response. Do not simulate a production outage by changing a real connection string or directing the route toward production infrastructure. The goal is to prove error translation, not to create collateral failure.

A Practical POST Route Handler Test Plan

For a mutation, separate transport parsing from domain validation. An empty body, malformed JSON and an unsupported content type are transport problems. A parsed object with a missing name or invalid email is a schema problem. A duplicate unique field, stale version or invalid state transition is a domain conflict. Keeping those categories visible makes status choices, logs and client messages easier to reason about.

A successful creation test should construct the same kind of Request a real client sends, call the route or send HTTP, assert the documented success status and inspect the returned representation. Then query the isolated database or repository to prove that normalized values were persisted. A response containing the submitted input is not proof that a write succeeded.

Malformed JSON deserves its own test when the handler promises a clean response. Wrap parsing at the appropriate boundary, return the project's chosen safe status and avoid echoing the raw parser exception. Validation failures should return stable field or issue information that clients can handle, but not internal schema objects whose shape may change across a library upgrade.

If the operation can conflict, create the prerequisite state inside the same case. For example, insert the first unique record and submit the second through the endpoint. Assert the documented conflict response and prove that only the intended record remains. Do not invent a 409 merely because it appears in a status table; use it only when the production route implements a real conflict.

Authentication Fixtures Without Secret Leakage

Authentication setup should be reusable without becoming invisible magic. A fixture can create a test account, obtain test-only state and expose either an isolated APIRequestContext or a browser context. Keep role and ownership explicit in the case so a reviewer can tell why access should be allowed. Store generated state outside version control and ensure traces, screenshots and CI artifacts have an appropriate retention policy.

Playwright distinguishes API contexts that share a BrowserContext cookie jar from standalone isolated contexts. Sharing state is useful when a browser journey and API assertion belong to one user flow. An isolated context is better when a test must prove that no browser cookies are present. Choose intentionally; otherwise an API test may pass because an unrelated browser login silently supplied the session.

Never place a bearer token, session value or webhook secret directly in a test title, assertion message, URL or snapshot. Load test-only credentials from the supported environment mechanism and redact request logging. A failed assertion that prints every response header can expose Set-Cookie values even when the production response is correct.

Uploads, Streaming and Health Routes

Only test these categories when they exist. For an upload route, use small harmless fixtures and cover the allowed type, rejected type, missing file, enforced size boundary, authentication and a controlled storage failure. Client-provided filename and content type are untrusted metadata; production validation determines the contract. Do not add malware samples or huge payloads to an ordinary application suite.

A streaming Route Handler needs Web Streams-aware assertions. Verify meaningful chunks, completion and safe cancellation behavior instead of converting a potentially unbounded stream into one giant buffer. Streaming behavior can depend on the runtime and deployment adapter, so retain at least one real HTTP check when it is a critical product feature.

A health endpoint should return minimal operational information, an intentional status and no credentials, query text, dependency addresses or exact private infrastructure versions. If readiness depends on services, keep checks bounded so the health request cannot amplify an outage. Test both the healthy contract and a controlled dependency failure without disconnecting production systems.

Review Tests as Security and Product Contracts

When reviewing an API test, ask what a pass actually proves. A pure schema test proves accepted input rules, not HTTP parsing. A direct handler test proves handler and Web API behavior, not filesystem routing or Proxy behavior. A mocked repository proves collaboration with that interface, not SQL constraints. A Playwright request proves the running route, but may still use a substitute service. Accurate labels prevent false confidence.

Review negative cases beside production code. Confirm authentication occurs before sensitive work, authorization checks the specific resource, validation precedes expensive operations, and failure mapping is neutral. For a denial test, inspect protected state afterward. For an external URL feature, verify rejection occurs before the outbound service connects. For a webhook, verify parsing and mutation happen only after signature validation.

Also inspect observability. Logs should help operators distinguish malformed input, expected denial, dependency failure and unexpected exceptions without storing credentials or sensitive request bodies. Tests can capture a controlled logger and assert an event category or correlation identifier, but should avoid freezing complete log strings. The public response and private diagnostic event have different audiences.

Finally, review parallel behavior. Unique email addresses, slugs, IDs, ports and database namespaces prevent worker collisions. Cleanup belongs in resilient hooks or disposable infrastructure and must remain scoped even when an assertion fails. If a group must run serially, document the shared resource; serial ordering should not hide accidental dependence between ordinary cases.

From Route Inventory to Running Suite

Start with a route inventory generated from the real repository. For each handler, record methods, input sources, authentication, authorization, persistence, outbound services, cookies, response headers, cache behavior and destructive side effects. Classify the endpoint as public read, public mutation, authenticated, authorization-sensitive, webhook, internal, database-backed, external proxy, upload or health. One endpoint may have several classifications.

Rank the inventory by consequence and change frequency. A payment webhook, ownership-sensitive mutation and externally consumed contract normally deserve more layers than an internal static lookup. Select three to six initial targets and create a matrix linking each risk to a test type. This is more useful than chasing a coverage percentage that treats trivial and critical lines equally.

Reuse the installed runner, aliases, setup files, fixtures and folder conventions. Do not create a second Vitest or Playwright configuration because a tutorial uses a different directory. Confirm the package manager from its lockfile, the Node version from the project's declared tooling, and the exact Next.js and React versions from the manifest and resolved lock data. Read current migration notes before changing async context or cache assumptions.

Run focused Vitest cases locally, then the complete configured suite. Build the application before Playwright, start it with test-only environment variables and wait for an observable readiness endpoint or log signal instead of a fixed sleep. Run the selected API and browser projects that CI will execute. Report passed, failed and skipped counts exactly as produced; if a tool is absent, say that it was not run.

Common Next.js API Testing Mistakes

  • Testing only the happy path.
  • Unit-testing helpers but never the HTTP contract.
  • Using E2E for every small rule.
  • Using a production database or production API keys.
  • Calling uncontrolled third parties in every run.
  • Ignoring authorization and object ownership.
  • Confusing 401 with 403.
  • Returning 200 for every failure.
  • Snapshotting huge JSON responses.
  • Sharing mutable records or depending on test order.
  • Hard-coding IDs that collide in parallel.
  • Ignoring malformed JSON and safe error output.
  • Mocking the entire Route Handler.
  • Calling a handler directly and claiming full runtime coverage.
  • Testing only the browser UI.
  • Assuming old caching behavior.
  • Skipping webhook signature checks where webhooks exist.

Next.js 16 API Testing Best Practices

  • Unit-test pure business rules.
  • Reuse real validation schemas.
  • Test Request and Response behavior.
  • Add real HTTP tests for critical endpoints.
  • Assert status, body and contract headers.
  • Separate authentication from authorization cases.
  • Use isolated databases and unique records.
  • Mock narrow service boundaries selectively.
  • Assert persisted state for important mutations.
  • Protect secrets, webhook signatures and traces.
  • Verify visible cache and revalidation behavior.
  • Keep tests independent and concurrency-safe.
  • Follow current framework and tool documentation.

Production API Testing Strategy

A practical CI pipeline runs fast checks first and widens confidence gradually. Build before Playwright so the suite tests the production-shaped application. Provide test-only environment values through CI secrets, start isolated dependencies, retain redacted artifacts for failures, and never run destructive requests against production.

Diagram 18: API testing CI pipelineStatic checks and Vitest gate the build before the server starts for real HTTP coverage.
Install
Lint + types
Vitest
Build
Start test server
Playwright → deploy

Testing & Quality Hub

#31 Vitest & React Testing LibraryPublished

#32 Playwright E2E TestingPublished

#33 Server Components, Client Components & Server Actions TestingPublished

#34 Route Handlers & API TestingPublished

#35 Authentication TestingPublished

#36 Database TestingPlanned

#37 Mocking APIs & External ServicesPlanned

#38 Playwright Authentication & User FlowsPlanned

#39 Testing with GitHub ActionsPlanned

#40 Production Testing StrategyPlanned

Frequently Asked Questions

How do I test Route Handlers in Next.js 16?

Use Vitest for pure rules and focused request/response tests, then Playwright API requests for critical endpoints over a running Next.js server.

Can I unit test a Route Handler?

Yes, but keep pure validation and business rules separate where practical. A direct handler test is closer to integration testing than a pure unit test.

Should I call the Route Handler directly in tests?

Direct calls are useful for handler and Web Request/Response behavior, but they bypass the Next.js router and do not replace real HTTP coverage.

How do I test a Next.js API over real HTTP?

Start the application in a safe test environment and use Playwright APIRequestContext through its request fixture and configured baseURL.

Can Playwright test APIs?

Yes. Playwright APIRequestContext supports GET, POST, PUT, PATCH, DELETE, headers, cookies, JSON data and response assertions.

How do I test GET Route Handlers?

Cover successful data, empty results, valid and invalid query values, authorization where required, missing resources and controlled service failures.

How do I test POST Route Handlers?

Cover valid JSON, malformed JSON, missing fields, validation failures, authorization, persistence, conflict behavior and safe failures that apply to the route.

How do I test request validation?

Test the same production schema with valid, invalid and boundary inputs, then verify the Route Handler maps failures to the documented response contract.

How do I test query parameters?

Construct URLs with expected, missing, malformed and out-of-range values. Use NextRequest only when the handler needs Next.js-specific request helpers.

How do I test dynamic route parameters?

Use the async context parameter shape required by the installed Next.js version and verify valid, malformed, found and missing identifiers.

How do I test authenticated APIs?

Use test-only identities and cover missing, valid and safely reproducible invalid or expired credentials without logging their values.

What is the difference between 401 and 403?

401 means valid authentication is missing or unacceptable. 403 means the caller is identified but is not permitted to perform the operation.

How do I test authorization and resource ownership?

Create a resource for test user A, authenticate as test user B, request the protected operation and verify denial plus unchanged state.

How do I test database writes safely?

Use a disposable test database or isolated schema, unique records and deterministic cleanup or transaction rollback. Assert both response and persisted state.

Should API tests use the production database?

No. Mutation suites must refuse production configuration and use isolated test data and credentials.

How do I test external API calls?

Mock a narrow service wrapper in focused tests and reserve a provider sandbox or controlled substitute for selected integration or E2E checks.

How do I test webhook Route Handlers?

Use provider-approved test signing, then cover a valid event, missing or invalid signature, malformed payload and duplicate delivery only when idempotency exists.

How do I test Route Handler errors?

Trigger known failure boundaries and assert the status, safe public body and unchanged state. Never expose stacks, SQL details, tokens or internal hosts.

How do I test rate limiting?

Against a local deterministic limiter, verify allowed requests, the configured threshold, 429 behavior and reset logic without bombarding production.

Should API tests run in CI?

Yes. Run focused tests before build, then start a test server and run selected Playwright API and browser journeys with isolated services.

Current Official References

Next Steps

Audit the actual Next.js application before creating its route matrix. Start with one public read, one validated mutation and one protected contract, then add focused Vitest coverage and a real HTTP Playwright check. Continue with Blog #35 for sessions, protected routes, roles and authenticated browser flows.

WhatsApp