Creating Users with POST

Creating Users with POST in a Remix API Route

Welcome! In this lesson, we’re going to add the “create” part of a simple Users API. You already have a Remix route serving data—now you’ll learn how that same route can accept data from the client and add a new user to the in-memory store.

You’ll work in app/routes/api.users.tsx to handle POST /api/users, validate the incoming JSON payload, generate a new user ID, and return the newly-created user with the right HTTP status code. Then you’ll see how the UI in app/routes/_index.tsx triggers that POST request so you can test everything right in the app.

Previously…

In the previous lesson, you served a list of users from an API route and learned how query parameters can shape a response. That’s the “read” side of an API. Now we’ll build the “create” side—accepting JSON input from the frontend and pushing a new record into the same in-memory users collection.

Where POST logic lives in Remix

In Remix API routes, loader() is for read-only requests (typically GET), and action() is for requests that change data (like POST, PUT, PATCH, and DELETE). In this project, both live in the same file: app/routes/api.users.tsx.

That means /api/users supports:

  • GET via loader() (returning the users array)
  • POST via action() (creating and returning a new user)
MethodURLRoute fileHandlerPurpose
GET/api/usersapi.users.tsxloaderList users
POST/api/usersapi.users.tsxactionCreate a user
GET/api/users/:idapi.users.$id.tsxloaderFetch one user
PUT/PATCH/DELETE/api/users/:idapi.users.$id.tsxactionReplace, partially update, or delete one user

Returning all users with the loader

This first block is the GET handler in app/routes/api.users.tsx. It returns the current in-memory users array as JSON.

// app/routes/api.users.tsx
import { json, type LoaderFunctionArgs, type ActionFunctionArgs } from '@remix-run/node';
import { users, type User } from '~/lib/data';

/**
 * Handles GET requests to /api/users.
 * Retrieves and returns a list of all users.
 */
export async function loader() {
  console.log('GET request to /api/users');
  return json(users);
}
  • The loader() here returns json(users), which means the response body is the raw array (not wrapped in { users: ... }). That’s important because the UI expects the endpoint to behave like a straightforward JSON API.
  • Even though Remix can pass { request } into a loader, this implementation doesn’t need the request object, because it always returns the full array exactly as it exists in memory.
  • The console.log(...) is a lightweight way to make requests visible while you’re developing and testing. When you click buttons in the UI, you can confirm which endpoint/method was hit.
Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal