Set up a production-ready REST API with TypeScript and Express — type-safe routes, Zod validation, JWT auth, error handling, and deployment to Render.

Abdur Razzak
Full-Stack Web Developer
TypeScript transforms the Express development experience. Without TypeScript, req.body is typed as any — you can access any property and only discover typos at runtime. With TypeScript, you define your request body shape and the compiler catches mismatches immediately. Type-safe route handlers, typed Mongoose models, and autocomplete on all objects make your API code dramatically easier to write and refactor safely.
Install: typescript, ts-node-dev, @types/node, @types/express, express, mongoose, zod, jsonwebtoken, bcryptjs, dotenv. Initialize with npx tsc --init. Key tsconfig.json settings: strict: true, target: 'ES2020', module: 'commonjs', outDir: './dist', rootDir: './src'. Add scripts: 'dev': 'ts-node-dev --respawn src/server.ts', 'build': 'tsc', 'start': 'node dist/server.js'. This gives you hot-reload in development and compiled JavaScript for production.
Create a validation middleware that takes a Zod schema and validates req.body, req.params, or req.query. If validation fails, return a 400 response with the Zod error messages formatted for the client. In your route handlers, use the validated and typed data — TypeScript now knows the exact shape of the request. Define reusable schemas: createBlogSchema, updateBlogSchema, paginationSchema. Zod's parse() throws on invalid data; safeParse() returns a result object without throwing.
Define a TypeScript interface for your document: interface IBlog { title: string; slug: string; content: string; publishedAt?: Date }. Use mongoose.model<IBlog>('Blog', blogSchema) to create a typed model. Now BlogModel.create(), BlogModel.find(), and BlogModel.findById() return properly typed documents. The Mongoose types package (@types/mongoose is built in for Mongoose 6+) provides full TypeScript integration including query types.
Create a custom AppError class that extends Error, adding statusCode and isOperational properties. In controllers, throw new AppError('Blog not found', 404). Your global error middleware (4-argument Express middleware) catches all thrown errors, formats them into a consistent JSON response, and logs unexpected errors. This pattern separates expected errors (client mistakes) from unexpected errors (bugs) and gives you centralized error logging.
In your Render service configuration, set the Build Command to npm install && npm run build and the Start Command to npm start (which runs node dist/server.js). Render runs the TypeScript compilation during build and serves the compiled JavaScript. Set all environment variables (MONGODB_URI, JWT_SECRET, PORT) in Render's dashboard. Enable Auto-Deploy from your main branch so every push triggers a new deployment automatically.