Unit 3: API Development and Server Actions

INT257 — Modern Web Application Development 9 min read

I. Orientation

Modern web applications separate user-interface rendering from operations that read and modify data. APIs provide structured communication between clients and servers, while server actions allow trusted server-side functions to be called directly from application components or forms. In frameworks such as Next.js, these mechanisms commonly use HTTP, URL routing, JavaScript or TypeScript, and server-side data access.

  • Governing principle: The client requests an operation; the server validates the request, performs authorized work, and returns a result.
  • HTTP convention: Requests contain a method, URL, headers, and sometimes a body; responses contain a status code, headers, and usually a body.
  • Separation of concerns: UI code handles interaction and presentation; server code handles secrets, database access, validation, and mutations.
  • Security assumption: Client input is untrusted, even when it originates from a controlled form or application interface.
  • Data convention: JSON is common for API data, while FormData is useful for forms and binary files.
  • State convention: After a mutation, the interface must either revalidate server data or update its local state.

II. Route Handlers

A. Route Handlers

Route handlers are server-side functions associated with URL paths that receive HTTP requests and produce HTTP responses.

  • Purpose: They expose custom endpoints such as /api/users or /api/orders.
  • File convention: In Next.js App Router, a route.ts or route.js file defines handlers inside an app directory.
  • Method exports: A handler usually exports functions named GET, POST, PUT, PATCH, or DELETE.
  • Server execution: Database credentials and private services can remain on the server because route-handler code is not sent to the browser.
  • Example:
TS
// app/api/status/route.ts
export async function GET() {
  return Response.json({ status: "ok" });
}
  • HTTP result: The example returns JSON with the default successful status, normally 200 OK.

III. REST APIs

A. REST APIs

A REST API models application data as resources identified by URLs and manipulated through standardized HTTP methods.

  • Resource naming: /api/products represents a collection; /api/products/42 represents product 42.
  • Statelessness: Each request contains the information needed to process it; the server does not rely on hidden client interaction history.
  • Representation: A product may be represented as JSON such as { "id": 42, "name": "Keyboard" }.
  • Uniform interface: Clients use predictable URL and method combinations rather than separate action-style URLs for every operation.
  • Status communication: 201 Created indicates successful creation, while 404 Not Found indicates that a requested resource does not exist.
  • Practical limitation: REST endpoints can require several network requests when a screen needs related resources; caching and endpoint design must address this cost.

IV. HTTP Methods

A. HTTP Methods

HTTP methods communicate the intended operation and help servers process requests consistently.

  • GET: Reads data and should not intentionally change server state. Example: GET /api/products/42.
  • POST: Creates a subordinate resource or starts an operation. Example: submitting a new product to /api/products.
  • PUT: Replaces a resource representation, commonly sending all required fields.
  • PATCH: Partially updates a resource, such as changing only price.
  • DELETE: Removes a resource, for example DELETE /api/products/42.
  • Idempotence: Repeating a PUT or DELETE should produce the same intended final state; repeating POST may create multiple records.
  • Safety: GET is considered safe because it is intended for retrieval, not mutation.

V. Dynamic Route Handlers

A. Dynamic Route Handlers

Dynamic route handlers use variable URL segments to process requests for individual resources.

  • Path convention: app/api/products/[id]/route.ts captures a URL such as /api/products/42.
  • Parameter access: The route parameter identifies which database record to read or modify.
  • Example:
TS
type Context = { params: Promise<{ id: string }> };

export async function GET(
  _request: Request,
  { params }: Context
) {
  const { id } = await params;
  return Response.json({ productId: id });
}
  • Type conversion: URL parameters arrive as strings, so "42" may need conversion with Number(id) before numeric database queries.
  • Validation: An invalid identifier should return 400 Bad Request, while a valid but missing record should return 404 Not Found.
  • Security boundary: A dynamic identifier must still be checked against the authenticated user’s permissions.

VI. Request and Response Handling

A. Request and Response Handling

Request and response handling converts HTTP data into usable values and communicates success or failure clearly.

  • Reading query data: new URL(request.url).searchParams.get("q") reads a query such as /api/products?q=usb.
  • Reading JSON: await request.json() parses a JSON body; malformed JSON should be handled as a client error.
  • Reading form data: await request.formData() returns text fields and uploaded files.
  • Inspecting headers: request.headers.get("Authorization") retrieves a bearer-token header when authentication uses tokens.
  • Response body: Response.json({ name: "Ada" }) serializes an object as JSON.
  • Explicit status:
TS
return Response.json(
  { error: "Product not found" },
  { status: 404 }
);
  • Error discipline: Do not expose database stack traces or secrets; return a stable public error message and log diagnostic details securely on the server.

VII. Server Actions

A. Server Actions

Server actions are asynchronous server functions that can be invoked by client interactions, especially forms, without requiring a separately designed API endpoint.

  • Declaration: A file or function can use the "use server" directive to mark server-only code.
  • Trusted location: Actions may access databases, environment variables, and private APIs directly.
  • Input rule: Arguments still originate from users and must be validated before database operations.
  • Return convention: An action can return structured state such as { success: false, error: "Invalid email" }.
  • Example:
TS
"use server";

export async function deletePost(id: string) {
  if (!/^\d+$/.test(id)) return { error: "Invalid ID" };
  // authorize user, then delete the record
  return { success: true };
}
  • API comparison: Route handlers are useful for mobile clients, third-party consumers, and explicit HTTP contracts; server actions are convenient for tightly integrated application mutations.
  • Authorization: Authentication must be checked inside the action because a client can attempt to invoke it directly.

VIII. Form Handling

A. Form Handling

Form handling connects browser controls to a server action or route endpoint while preserving accessible, progressively enhanced behavior.

  • Native submission: An HTML <form> submits named controls; a control without a name is generally absent from submitted form data.
  • Action binding: A server action can be supplied to a form’s action attribute.
  • Example:
TSX
<form action={createUser}>
  <input name="email" type="email" required />
  <button type="submit">Create user</button>
</form>
  • Server extraction: The action receives FormData, from which formData.get("email") retrieves the submitted value.
  • Pending state: A submit button should be disabled or indicate progress while the action is running, preventing accidental duplicate submissions.
  • Accessibility: Use labels connected with htmlFor, meaningful button text, and an error region such as aria-live="polite".

IX. Form Validation

A. Form Validation

Form validation verifies that submitted values have the correct shape, type, range, and business meaning before processing.

  • Client validation: HTML attributes such as required, minLength={8}, and type="email" provide immediate feedback but cannot be trusted alone.
  • Server validation: The server repeats validation because requests can bypass the browser entirely.
  • Schema validation: Libraries such as Zod can define a single explicit schema:
TS
const schema = z.object({
  email: z.string().email(),
  age: z.coerce.number().int().min(18)
});
  • Error structure: Return field-specific errors, for example { fieldErrors: { email: ["Invalid email"] } }, so the UI can place feedback beside the correct control.
  • Normalization: Trim whitespace and normalize values before checking them, such as email.trim().toLowerCase().
  • Business rules: Validation may include uniqueness, ownership, or allowed state transitions, such as rejecting an already-registered email.
  • Security: Validation limits malformed input; authorization separately determines whether the user may perform the operation.

X. File Upload

A. File Upload

File upload transfers binary content from a form to a server, where type, size, storage, and access must be controlled.

  • Form encoding: A file form requires encType="multipart/form-data":
TSX
<form action={uploadAvatar} encType="multipart/form-data">
  <input name="avatar" type="file" accept="image/png,image/jpeg" />
  <button type="submit">Upload</button>
</form>
  • Extraction: formData.get("avatar") may return a File; verify value instanceof File before using it.
  • Size validation: Reject a file above an application limit, such as 5 * 1024 * 1024 bytes for 5 MB.
  • Type validation: Check MIME type and preferably inspect file content; a renamed executable must not be trusted merely because its filename ends in .jpg.
  • Storage choice: Store large files in object storage and save only a controlled URL or key in the database.
  • Filename safety: Generate a server-side filename or object key rather than using raw user filenames.
  • Access control: Private uploads require authenticated, authorized download paths or signed URLs.

XI. Data Mutations

A. Data Mutations

Data mutations are operations that create, update, or delete persistent application state.

  • Transaction boundary: Related writes should use a database transaction so either all required changes commit or none do.
  • Authorization order: Authenticate the caller, authorize the specific record, validate input, then mutate data.
  • Example flow:
TS
const user = await requireUser();
const input = schema.parse(rawInput);
await db.post.update({
  where: { id: input.id, authorId: user.id },
  data: { title: input.title }
});
  • Cache consistency: After changing data, invalidate or revalidate affected paths so later reads do not display stale information.
  • Concurrency: Conditional updates, version columns, or database constraints help prevent overwriting another user’s changes.
  • Failure handling: Return a predictable error state and avoid claiming success until the database operation completes.
  • Idempotency: For payment or retry-sensitive operations, an idempotency key can prevent duplicate effects from repeated requests.

XII. Optimistic UI Updates

A. Optimistic UI Updates

Optimistic UI updates display the expected result immediately and reconcile it with the server response afterward.

  • Interaction model: When a user likes a post, the interface may increase likes from 10 to 11 before the request finishes.
  • Latency benefit: The application feels immediate because rendering does not wait for network and database completion.
  • Temporary state: Mark the item as pending so repeated clicks do not send conflicting mutations.
  • Rollback: If the server rejects the operation, restore the previous value and show an actionable error.
  • Server authority: The final server response wins because permissions, validation, and concurrent changes are decided on the server.
  • Example pattern:
TS
setLikes(value => value + 1);
try {
  await likePost(postId);
} catch {
  setLikes(value => value - 1);
}
  • Suitable operations: Optimistic updates work well for reversible actions such as toggling a like or completing a task.
  • Limitations: They are riskier for payments, destructive deletion, inventory counts, or operations whose server result cannot be predicted reliably.