Site icon The Word 360

How to Build a REST API with Node.js, Express, and TypeScript in 2026

How to Build a REST API with Node.js, Express, and TypeScript in 2026

How to Build a REST API with Node.js, Express, and TypeScript in 2026

&Tab;&Tab;<div class&equals;"wpcnt">&NewLine;&Tab;&Tab;&Tab;<div class&equals;"wpa">&NewLine;&Tab;&Tab;&Tab;&Tab;<span class&equals;"wpa-about">Advertisements<&sol;span>&NewLine;&Tab;&Tab;&Tab;&Tab;<div class&equals;"u top&lowbar;amp">&NewLine;&Tab;&Tab;&Tab;&Tab;&Tab;&Tab;&Tab;<amp-ad width&equals;"300" height&equals;"265"&NewLine;&Tab;&Tab; type&equals;"pubmine"&NewLine;&Tab;&Tab; data-siteid&equals;"173035871"&NewLine;&Tab;&Tab; data-section&equals;"1">&NewLine;&Tab;&Tab;<&sol;amp-ad>&NewLine;&Tab;&Tab;&Tab;&Tab;<&sol;div>&NewLine;&Tab;&Tab;&Tab;<&sol;div>&NewLine;&Tab;&Tab;<&sol;div><p dir&equals;"ltr">To build a REST API in 2026&comma; install Node&period;js 22&period;18 or later&comma; Express 5&comma; and TypeScript&comma; then write route handlers that validate input and return correct HTTP status codes&period; Node can now run TypeScript files directly by stripping types&comma; so you no longer need ts-node for simple projects&period; This guide walks through setup&comma; a small CRUD API&comma; validation with Zod&comma; error handling&comma; and status codes&period; It also flags what changed since older tutorials&colon; Express 5 handles rejected promises for you&comma; and TypeScript 7 has stricter defaults&period;<&sol;p>&NewLine;<h2 dir&equals;"ltr">What Changed Since the Old Tutorials<&sol;h2>&NewLine;<h3 dir&equals;"ltr"><strong>TypeScript 7<&sol;strong><&sol;h3>&NewLine;<p dir&equals;"ltr">Microsoft&&num;8217&semi;s announcement says TypeScript 7&period;0 is &&num;8220&semi;a 10x faster native port&comma;&&num;8221&semi; written in Go&comma; and the command is still tsc&period; TypeScript 6&period;0&comma; released March 23&comma; 2026&comma; was the last JavaScript-based version&period; The post lists breaking changes&colon; strict now defaults to true&comma; module defaults to esnext&comma; and options like target es5&comma; baseUrl&comma; and the older node module resolution modes are hard errors&period; There is no programmatic API until 7&period;1&comma; so tools like typescript-eslint use a compatibility package&period; As of October 17&comma; 2026&comma; the npm registry listed TypeScript 7&period;0&period;2&period;<&sol;p>&NewLine;<h3 dir&equals;"ltr"><strong>Node Runs TypeScript Directly<&sol;strong><&sol;h3>&NewLine;<p dir&equals;"ltr">Node&&num;8217&semi;s documentation marks type stripping as stable&period; It is on by default since versions 23&period;6&period;0 and 22&period;18&period;0&comma; stable in 25&period;2&period;0 and 24&period;12&period;0&comma; and prints no warning since 24&period;3&period;0&period; The limits matter&colon; it does no type checking&comma; enums&comma; runtime namespaces&comma; and parameter properties cause errors&comma; tsconfig paths are ignored&comma; imports need the &period;ts extension&comma; type imports need the type keyword&comma; and &period;tsx and files under node&lowbar;modules are not supported&period;<&sol;p>&NewLine;<h3 dir&equals;"ltr"><strong>Express 5<&sol;strong><&sol;h3>&NewLine;<p dir&equals;"ltr">Express 5 requires Node 18 or higher&period; The npm latest tag for express was 5&period;3&period;0&comma; published October 9&comma; 2026&comma; with a latest-4 tag at 4&period;22&period;3&period; Express&&num;8217&semi;s migration guide says handlers and middleware that return rejected promises now forward the error to error middleware automatically&period; It also notes that req&period;body is undefined when unparsed&comma; that app&period;del&lpar;&rpar; and req&period;param&lpar;&rpar; are removed&comma; and that wildcards need a name&comma; such as &sol;&ast;splat&period;<&sol;p>&NewLine;<h3 dir&equals;"ltr"><strong>Which Node Version&quest;<&sol;strong><&sol;h3>&NewLine;<p dir&equals;"ltr">The Node&period;js release pages list v24 as Active LTS and v26 as Current&comma; first released May 5&comma; 2026&period; The release schedule says v26 enters LTS on October 28&comma; 2026&comma; and Node 24 enters maintenance on October 20&comma; 2026&period; Node 20 is end-of-life&period; Use an LTS version for anything you deploy&period;<&sol;p>&NewLine;<h2 dir&equals;"ltr">Step 1&colon; Set Up the Project<&sol;h2>&NewLine;<p dir&equals;"ltr">Following Express&&num;8217&semi;s installation guide&comma; create a folder and initialize it&comma; then install packages&period; The commands are mkdir myapp&comma; cd myapp&comma; npm init&comma; npm install express&comma; and npm install &&num;8211&semi;save-dev typescript &commat;types&sol;express &commat;types&sol;node&period; Add Zod with npm install zod&period; Zod&&num;8217&semi;s documentation says it is tested against TypeScript 5&period;5 or newer and that you must enable strict mode&period;<&sol;p>&NewLine;<h3 dir&equals;"ltr"><strong>A tsconfig for Node<&sol;strong><&sol;h3>&NewLine;<p dir&equals;"ltr">Node&&num;8217&semi;s documentation recommends this configuration for TypeScript 5&period;8 or newer&comma; which is used here for type checking only&colon;<&sol;p>&NewLine;<div dir&equals;"ltr" tabindex&equals;"0" role&equals;"group" aria-label&equals;"Code">&NewLine;<div>&NewLine;<div><&sol;div>&NewLine;<&sol;div>&NewLine;<div>&NewLine;<pre><code>&lbrace; &NewLine; "compilerOptions"&colon; &lbrace; &NewLine; "noEmit"&colon; true&comma; &NewLine; "target"&colon; "esnext"&comma; &NewLine; "module"&colon; "nodenext"&comma; &NewLine; "rewriteRelativeImportExtensions"&colon; true&comma; &NewLine; "erasableSyntaxOnly"&colon; true&comma; &NewLine; "verbatimModuleSyntax"&colon; true &NewLine; &rcub; &NewLine;&rcub;<&sol;code><&sol;pre>&NewLine;<&sol;div>&NewLine;<&sol;div>&NewLine;<p dir&equals;"ltr">The erasableSyntaxOnly setting makes TypeScript flag syntax that type stripping cannot handle&comma; such as enums&period; Run npx tsc to check types&comma; and run the app with node app&period;ts&period; Express&&num;8217&semi;s own docs say running TypeScript directly needs Node 22&period;18&period;0 or newer &lpar;or 23&period;6&period;0 or newer&rpar; and TypeScript 5&period;8 or newer&period;<&sol;p>&NewLine;<h2 dir&equals;"ltr">Step 2&colon; Hello World<&sol;h2>&NewLine;<p dir&equals;"ltr">This is the official Express example&comma; written in CommonJS&colon;<&sol;p>&NewLine;<div dir&equals;"ltr" tabindex&equals;"0" role&equals;"group" aria-label&equals;"Code">&NewLine;<div>&NewLine;<div><&sol;div>&NewLine;<&sol;div>&NewLine;<div>&NewLine;<pre><code>const express &equals; require&lpar;'express'&rpar;&semi; &NewLine;const app &equals; express&lpar;&rpar;&semi; &NewLine;const port &equals; 3000&semi; &NewLine; &NewLine;app&period;get&lpar;'&sol;'&comma; &lpar;req&comma; res&rpar; &equals;&gt&semi; &lbrace; &NewLine; res&period;send&lpar;'Hello World&excl;'&rpar;&semi; &NewLine;&rcub;&rpar;&semi; &NewLine; &NewLine;app&period;listen&lpar;port&comma; &lpar;&rpar; &equals;&gt&semi; &lbrace; &NewLine; console&period;log&lpar;&grave;Example app listening on port &dollar;&lbrace;port&rcub;&grave;&rpar;&semi; &NewLine;&rcub;&rpar;&semi;<&sol;code><&sol;pre>&NewLine;<&sol;div>&NewLine;<&sol;div>&NewLine;<p dir&equals;"ltr">In a TypeScript file you would use import syntax instead&comma; with the type keyword for type-only imports&comma; and the filename would end in &period;ts&period; Run it&comma; open the browser to port 3000&comma; and you should see the greeting&period;<&sol;p>&NewLine;<h2 dir&equals;"ltr">Step 3&colon; Design the Resource<&sol;h2>&NewLine;<p dir&equals;"ltr">REST organizes an API around resources&period; For a list of users&comma; a typical layout is a GET request on the users collection to list&comma; a POST on the same path to create&comma; a GET on a single user path to read&comma; a PUT or PATCH to update&comma; and a DELETE to remove&period; Keep paths as nouns&comma; not verbs&period;<&sol;p>&NewLine;<h2 dir&equals;"ltr">Step 4&colon; Validate Input With Zod<&sol;h2>&NewLine;<p dir&equals;"ltr">Never trust a request body&period; Zod&&num;8217&semi;s documentation shows the pattern&colon; define a schema&comma; then parse input against it&period; For example&comma; a schema built with z&period;object and a name field of z&period;string&lpar;&rpar; is used by calling its parse method on the input&period; When parsing fails&comma; it throws an error you can turn into a 400 or 422 response&period; Remember that Express 5 leaves req&period;body undefined unless you add the JSON body parser middleware&comma; so enable it first and check for a missing body&period;<&sol;p>&NewLine;<h2 dir&equals;"ltr">Step 5&colon; Use the Right Status Codes<&sol;h2>&NewLine;<p dir&equals;"ltr">MDN&&num;8217&semi;s reference gives these meanings&colon; 200 OK&semi; 201 Created&comma; &&num;8220&semi;typically the response sent after POST&&num;8221&semi;&semi; 204 No Content&semi; 400 Bad Request&semi; 401&comma; &&num;8220&semi;semantically &&num;8230&semi; unauthenticated&&num;8221&semi;&semi; 403 Forbidden&semi; 404 Not Found&semi; 409 Conflict&semi; 422 Unprocessable Content&semi; and 500 Internal Server Error&period; A reasonable pattern is 201 after creating&comma; 204 after deleting&comma; 400 for malformed requests&comma; 404 when the resource is missing&comma; and 409 when it conflicts with existing data&period;<&sol;p>&NewLine;<h2 dir&equals;"ltr">Step 6&colon; Handle Errors<&sol;h2>&NewLine;<p dir&equals;"ltr">With Express 5&comma; an async handler can simply throw or reject&comma; and the error reaches your error middleware&period; Express&&num;8217&semi;s documentation shows this pattern&colon;<&sol;p>&NewLine;<div dir&equals;"ltr" tabindex&equals;"0" role&equals;"group" aria-label&equals;"Code">&NewLine;<div>&NewLine;<div><&sol;div>&NewLine;<&sol;div>&NewLine;<div>&NewLine;<pre><code>app&period;get&lpar;'&sol;user&sol;&colon;id'&comma; async &lpar;req&comma; res&rpar; &equals;&gt&semi; &lbrace; &NewLine; const user &equals; await getUserById&lpar;req&period;params&period;id&rpar;&semi; &NewLine; res&period;send&lpar;user&rpar;&semi; &NewLine;&rcub;&rpar;&semi; &NewLine; &NewLine;function errorHandler&lpar;err&comma; req&comma; res&comma; next&rpar; &lbrace; &NewLine; if &lpar;res&period;headersSent&rpar; &lbrace; &NewLine; return next&lpar;err&rpar;&semi; &NewLine; &rcub; &NewLine; res&period;status&lpar;500&rpar;&semi; &NewLine; res&period;render&lpar;'error'&comma; &lbrace; error&colon; err &rcub;&rpar;&semi; &NewLine;&rcub;<&sol;code><&sol;pre>&NewLine;<&sol;div>&NewLine;<&sol;div>&NewLine;<p dir&equals;"ltr">The documentation also says the response status is set from err&period;status or err&period;statusCode&comma; and &&num;8220&semi;If this value is outside the 4xx or 5xx range&comma; it will be set to 500&period;&&num;8221&semi; For a JSON API&comma; send a JSON body instead of rendering a page&comma; and never leak stack traces to clients&period;<&sol;p>&NewLine;<h2 dir&equals;"ltr">Step 7&colon; Organize Your Code<&sol;h2>&NewLine;<p dir&equals;"ltr">Keep routes&comma; validation schemas&comma; and business logic in separate files&period; A common layout has a routes folder&comma; a schemas folder&comma; and a services folder&period; This article does not claim a standard&comma; since the sources reviewed do not prescribe one&period;<&sol;p>&NewLine;<h2 dir&equals;"ltr">Testing Your API<&sol;h2>&NewLine;<p dir&equals;"ltr">Use a tool such as curl or an HTTP client extension to send requests and check responses&period; Test the unhappy paths&colon; missing fields&comma; wrong types&comma; unknown ids&period; The sources reviewed do not cover testing frameworks&comma; so this article does not recommend one&period;<&sol;p>&NewLine;<h2 dir&equals;"ltr">Security Basics<&sol;h2>&NewLine;<p dir&equals;"ltr">Validate all input&comma; limit body size&comma; and use HTTPS in production&period; Do not log secrets&period; Keep dependencies updated&period; The research for this article did not review authentication libraries&comma; so choose one after reading its current documentation&period;<&sol;p>&NewLine;<h2 dir&equals;"ltr">Deployment<&sol;h2>&NewLine;<p dir&equals;"ltr">Pick an LTS Node version&comma; install dependencies&comma; set environment variables&comma; and run with a process manager or container&period; The free hosting options in our deployment guide may suit hobby projects&period;<&sol;p>&NewLine;<h2 dir&equals;"ltr">Project Structure That Scales<&sol;h2>&NewLine;<p dir&equals;"ltr">A small API can live in one file&comma; but a slightly larger one benefits from separation&period; Keep route definitions in one module&comma; validation schemas in another&comma; and data access in a third&period; That way&comma; when you swap the in-memory store for a database&comma; only one module changes&period; The sources reviewed do not prescribe a layout&comma; so treat this as a common-sense suggestion&comma; not a rule from Express&&num;8217&semi;s documentation&period;<&sol;p>&NewLine;<h2 dir&equals;"ltr">Handling Validation Errors Cleanly<&sol;h2>&NewLine;<p dir&equals;"ltr">When a Zod parse fails&comma; return a 400 or 422 with a message the client can use&comma; and do not leak internal details&period; MDN describes 422 as Unprocessable Content&comma; which suits a request that is well formed but has invalid values&comma; while 400 suits one that is malformed&period; Pick one convention and document it&comma; so API consumers know what to expect&period; In Express 5&comma; you can throw from an async handler and let your error middleware format the response&comma; which keeps handlers short&period;<&sol;p>&NewLine;<h2 dir&equals;"ltr">Checking Your Setup After Upgrading<&sol;h2>&NewLine;<p dir&equals;"ltr">Because TypeScript 7 changed defaults&comma; a project that compiled under older settings may produce errors&period; The release post says strict now defaults to true&comma; module defaults to esnext&comma; and several old options are hard errors&comma; including target es5 and baseUrl&period; If you hit these&comma; update tsconfig rather than turning checks off&period; Also note the compatibility detail for tooling&colon; until version 7&period;1 there is no programmatic API&comma; so linters that depend on it use a compatibility package&period; If an editor integration breaks after an upgrade&comma; check whether it relies on that API&period;<&sol;p>&NewLine;<h2 dir&equals;"ltr">A Short Reading Order for Beginners<&sol;h2>&NewLine;<p dir&equals;"ltr">If you are new to all three technologies&comma; learn them in this order&period; First&comma; plain Node with a simple script&comma; using the stable type stripping in a current LTS release&period; Second&comma; Express with a single route&comma; such as the hello world example&period; Third&comma; TypeScript types for request and response bodies&comma; checked with npx tsc&period; Fourth&comma; Zod for validation&period; Fifth&comma; error middleware&period; Each step adds one idea&comma; and each can be tested before the next&period;<&sol;p>&NewLine;<h2 dir&equals;"ltr">Express 5 Gotchas to Remember<&sol;h2>&NewLine;<p dir&equals;"ltr">Express 5 changed several things that older tutorials get wrong&period; Wildcard routes need a name&comma; such as &sol;&ast;splat&period; The app&period;del&lpar;&rpar; and req&period;param&lpar;&rpar; methods are removed&period; The req&period;body property is undefined unless you use a body parser&period; And the node version floor is 18&period; If a tutorial code sample fails with a path-pattern error&comma; the wildcard rule is the most likely cause&period;<&sol;p>&NewLine;<h2 dir&equals;"ltr">Staying Current<&sol;h2>&NewLine;<p dir&equals;"ltr">Node and TypeScript move quickly&period; As of October 17&comma; 2026&comma; the schedule shows Node 24 entering maintenance on October 20&comma; 2026 and Node 26 entering LTS on October 28&comma; 2026&comma; so use an LTS release when starting a project&period; Check the TypeScript release notes before upgrading&comma; since version 7 changes defaults&period; Pin versions in package&period;json so your builds are repeatable&period;<&sol;p>&NewLine;<h2 dir&equals;"ltr">From In-Memory to Real Database<&sol;h2>&NewLine;<p dir&equals;"ltr">A tutorial API often keeps data in an array&comma; which resets whenever the server restarts&period; When you outgrow that&comma; add a database through a module that your routes call&comma; so the route code does not change&period; The free database options covered in our hosting guide are a starting point for prototypes&period; The research for this article did not review database libraries for Node&comma; so choose one after reading its current documentation&comma; and confirm that it supports your TypeScript version&period;<&sol;p>&NewLine;<h2 dir&equals;"ltr">Deploying With Confidence<&sol;h2>&NewLine;<p dir&equals;"ltr">Before deploying&comma; check that your build runs from a clean install&comma; that environment variables are set on the host&comma; and that you use an LTS Node version&period; Run npx tsc as a check step in your build&comma; since type stripping at runtime does not check types&period; Add a simple health route that returns 200&comma; so hosting platforms and monitors can confirm the service is running&period; Log errors with enough context to debug&comma; without logging secrets or personal data&period;<&sol;p>&NewLine;<h2 dir&equals;"ltr">Frequently Asked Questions<&sol;h2>&NewLine;<h3 dir&equals;"ltr">Do I still need ts-node&quest;<&sol;h3>&NewLine;<p dir&equals;"ltr">For simple projects&comma; no&period; Node&&num;8217&semi;s stable type stripping runs &period;ts files directly&period; The npm registry shows ts-node 10&period;9&period;2 last published in December 2023&period;<&sol;p>&NewLine;<h3 dir&equals;"ltr">Should I use Express 5 or 4&quest;<&sol;h3>&NewLine;<p dir&equals;"ltr">Express 5 is the npm latest&period; It forwards async errors to error middleware&period; The latest-4 tag is 4&period;22&period;3&period;<&sol;p>&NewLine;<h3 dir&equals;"ltr">Does Node type-check my TypeScript&quest;<&sol;h3>&NewLine;<p dir&equals;"ltr">No&period; Type stripping removes types without checking&period; Run npx tsc for checks&period;<&sol;p>&NewLine;<h3 dir&equals;"ltr">Which status code should POST return&quest;<&sol;h3>&NewLine;<p dir&equals;"ltr">201 Created&comma; typically&comma; per MDN&period;<&sol;p>&NewLine;<h3 dir&equals;"ltr">Which Node version should I use&quest;<&sol;h3>&NewLine;<p dir&equals;"ltr">An LTS release&period; Node 24 is LTS&comma; and Node 26 becomes LTS on October 28&comma; 2026&period;<&sol;p>&NewLine;<h2 dir&equals;"ltr"><strong>References and Sources<&sol;strong><&sol;h2>&NewLine;<p dir&equals;"ltr">Node&period;js&comma; &&num;8220&semi;Previous Releases&&num;8221&semi;&colon; <a href&equals;"https&colon;&sol;&sol;nodejs&period;org&sol;en&sol;about&sol;previous-releases">https&colon;&sol;&sol;nodejs&period;org&sol;en&sol;about&sol;previous-releases<&sol;a><&sol;p>&NewLine;<p dir&equals;"ltr">Node&period;js&comma; &&num;8220&semi;Download&&num;8221&semi;&colon; <a href&equals;"https&colon;&sol;&sol;nodejs&period;org&sol;en&sol;download">https&colon;&sol;&sol;nodejs&period;org&sol;en&sol;download<&sol;a><&sol;p>&NewLine;<p dir&equals;"ltr">GitHub&comma; &&num;8220&semi;nodejs&sol;Release&&num;8221&semi;&colon; <a href&equals;"https&colon;&sol;&sol;github&period;com&sol;nodejs&sol;Release">https&colon;&sol;&sol;github&period;com&sol;nodejs&sol;Release<&sol;a><&sol;p>&NewLine;<p dir&equals;"ltr">Node&period;js&comma; &&num;8220&semi;End-of-life releases&&num;8221&semi;&colon; <a href&equals;"https&colon;&sol;&sol;nodejs&period;org&sol;en&sol;about&sol;eol">https&colon;&sol;&sol;nodejs&period;org&sol;en&sol;about&sol;eol<&sol;a><&sol;p>&NewLine;<p dir&equals;"ltr">Node&period;js Docs&comma; &&num;8220&semi;Modules&colon; TypeScript&&num;8221&semi;&colon; <a href&equals;"https&colon;&sol;&sol;nodejs&period;org&sol;api&sol;typescript&period;html">https&colon;&sol;&sol;nodejs&period;org&sol;api&sol;typescript&period;html<&sol;a><&sol;p>&NewLine;<p dir&equals;"ltr">Microsoft&comma; &&num;8220&semi;Announcing TypeScript 7&period;0&&num;8221&semi;&colon; <a href&equals;"https&colon;&sol;&sol;devblogs&period;microsoft&period;com&sol;typescript&sol;announcing-typescript-7-0&sol;">https&colon;&sol;&sol;devblogs&period;microsoft&period;com&sol;typescript&sol;announcing-typescript-7-0&sol;<&sol;a><&sol;p>&NewLine;<p dir&equals;"ltr">Express&comma; &&num;8220&semi;Migrating to Express 5&&num;8221&semi;&colon; <a href&equals;"https&colon;&sol;&sol;expressjs&period;com&sol;en&sol;guide&sol;migrating-5&period;html">https&colon;&sol;&sol;expressjs&period;com&sol;en&sol;guide&sol;migrating-5&period;html<&sol;a><&sol;p>&NewLine;<p dir&equals;"ltr">Express&comma; &&num;8220&semi;Error handling&&num;8221&semi;&colon; <a href&equals;"https&colon;&sol;&sol;expressjs&period;com&sol;en&sol;guide&sol;error-handling&period;html">https&colon;&sol;&sol;expressjs&period;com&sol;en&sol;guide&sol;error-handling&period;html<&sol;a><&sol;p>&NewLine;<p dir&equals;"ltr">Express&comma; &&num;8220&semi;Installing&&num;8221&semi;&colon; <a href&equals;"https&colon;&sol;&sol;expressjs&period;com&sol;en&sol;starter&sol;installing&period;html">https&colon;&sol;&sol;expressjs&period;com&sol;en&sol;starter&sol;installing&period;html<&sol;a><&sol;p>&NewLine;<p dir&equals;"ltr">Express&comma; &&num;8220&semi;Hello world example&&num;8221&semi;&colon; <a href&equals;"https&colon;&sol;&sol;expressjs&period;com&sol;en&sol;starter&sol;hello-world&period;html">https&colon;&sol;&sol;expressjs&period;com&sol;en&sol;starter&sol;hello-world&period;html<&sol;a><&sol;p>&NewLine;<p dir&equals;"ltr">Zod&comma; &&num;8220&semi;Zod documentation&&num;8221&semi;&colon; <a href&equals;"https&colon;&sol;&sol;zod&period;dev&sol;">https&colon;&sol;&sol;zod&period;dev&sol;<&sol;a><&sol;p>&NewLine;<p dir&equals;"ltr">MDN&comma; &&num;8220&semi;HTTP response status codes&&num;8221&semi;&colon; <a href&equals;"https&colon;&sol;&sol;developer&period;mozilla&period;org&sol;en-US&sol;docs&sol;Web&sol;HTTP&sol;Reference&sol;Status">https&colon;&sol;&sol;developer&period;mozilla&period;org&sol;en-US&sol;docs&sol;Web&sol;HTTP&sol;Reference&sol;Status<&sol;a><&sol;p>&NewLine;

Exit mobile version