How to Migrate React to TypeScript (API Module)

When you migrate React to TypeScript, the API module is the right place to start - it's the boundary where shape mismatches cause the most runtime damage. Add a named type, explicit parameter types, and Promise<T> return types, then prove it with tsc --strict.

Fullstack Engineertypescriptreactrefactor

Why start with the API layer

The API module is where JS fetch calls return any, where you pass the wrong field to a downstream component, and where TypeScript pays off fastest. Converting it first gives you typed data flowing into the rest of the app.

A plain-JS API module looks like this:

// api.js - no types, fetch returns any
export async function getUser(id) {
  const r = await fetch(`/api/users/${id}`);
  return r.json();
}

The compiler doesn't know what getUser returns, so any caller can read a non-existent field silently. TypeScript catches that at build time.

Define your data type first

A type alias names the shape once. Every function signature and return type references it, so a schema change ripples as a compile error everywhere:

type User = { id: number; name: string; email: string };

Write the typed module

Convert api.js to api.ts with explicit parameter types, Promise<T> return types, and ESM exports:

type User = { id: number; name: string; email: string };

export async function getUser(id: number): Promise<User> {
  const r = await fetch(`/api/users/${id}`);
  return r.json();
}

export async function listUsers(): Promise<User[]> {
  const r = await fetch("/api/users");
  const d = await r.json();
  return d.data;
}

export async function createUser(name: string, email: string): Promise<User> {
  const r = await fetch("/api/users", {
    method: "POST",
    body: JSON.stringify({ name, email }),
  });
  return r.json();
}

Note that listUsers unwraps d.data - the fetch response is a wrapper object, not the array directly. A JS caller would get this silently wrong; the TS return type Promise<User[]> makes the compiler demand a User[], so the unwrap is explicit and required.

Verify with tsc --strict

--strict enables noImplicitAny, strictNullChecks, and several other checks. Run it without emitting files to use it as a pure type-checker:

tsc --strict --noEmit --target es2020 --moduleResolution bundler --module esnext api.ts

Clean output (no errors) means no implicit any has leaked through. If you see an error, TypeScript tells you exactly which parameter or return value is untyped.

What to do after the API module

Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.

Try it in a workspace →

What you'll practice

FAQ

How do I migrate a React app from JavaScript to TypeScript?

Rename .js files to .ts/.tsx one at a time, starting with leaf modules like your API layer. Add explicit types to function parameters and return values, then run tsc --strict --noEmit to catch implicit-any leaks before moving to the next file.

What is Promise<User> vs Promise<any> in TypeScript?

Promise<User> tells the compiler the resolved value matches the User type. If a caller reads a field that doesn't exist on User, or passes the wrong type, it's a compile error. Promise<any> opts out of all those checks - effectively the same as plain JS.

Do I need to use module.exports or export in a TypeScript file?

Use ESM exports (export function / export default) in TypeScript. CommonJS module.exports works but loses the type information when imported by other TS files and doesn't match how React toolchains (Vite, Next.js) bundle modules.

Keep learning

Fix a React useState Stale-State BugFullstack projectFix a Blank React Dashboard (Failed Fetch)Fullstack projectFix the React Search That Won't Filter (useEffect Deps)Fullstack projectFullstack roadmapStep by step to hiredFullstack interview questionsSTAR answersAll Fullstack projectsProjects hub

Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.

Open the workspace →