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.
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
- Generate types from your backend schema (openapi-typescript, Zod
z.infer, Prisma client) soUseris derived from the source of truth, not hand-written in the frontend. - Add
tsconfig.jsonto the project and set"strict": trueso every file gets the same checks automatically. - Migrate component props next: add
interface Propsto each component and replaceReact.FCwith explicit typed function signatures.
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
- Writing a named type and using it as a Promise<T> return type
- Converting CommonJS module.exports to ESM export function
- Running tsc --strict --noEmit as a pure type-checker (no emit)
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
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 →