Because nothing in the run path checks types, something else has to. tsc --noEmit reads the config from tsconfig.json, reports every error, and exits non-zero if there are any.
$ npx tsc --noEmit src/bad.ts(2,21): error TS2322: Type 'string' is not assignable to type 'number'. src/bad.ts(3,24): error TS2551: Property 'toFixed' does not exist on type 'string'. Did you mean 'fixed'? $ echo $? 1
Run that same file under Node and it starts happily, gets through the assignment, and dies at the method call with TypeError: greet(...).toFixed is not a function — one error instead of two, found later.
"scripts": {
"dev": "node --watch --env-file=.env src/server.ts",
"typecheck": "tsc --noEmit",
"build": "tsc -p tsconfig.build.json",
"start": "node --enable-source-maps dist/server.js"
}node --run typecheck (Watch Mode and Scripts) invokes these without npm 2,036 's process overhead. In CI a job that runs npm ci, npm run typecheck and node --test is enough, and on TypeScript 7 it is fast enough to run on every branch.
tsconfig.build.json extends the checking config and turns emit on: "noEmit": false, "outDir": "dist", "rootDir": "src" and "sourceMap": true. rewriteRelativeImportExtensions then does the one thing that makes this workflow hold together:
import { createServer } from 'node:http';
import { greet } from './greet.js';
const port = Number(process.env.PORT ?? 3000);
const ada = { id: 1, name: 'Ada' };
createServer((_req, res) => res.end(greet(ada))).listen(port, () => {
console.log(`listening on ${port}`);
});
//# sourceMappingURL=server.js.mapThe ./greet.ts specifier became ./greet.js, so the same source ran unbuilt in development and runs built in production; --enable-source-maps in the start script maps stack traces back to the .ts lines.
Is the build step even necessary? You can copy src into the image and run node src/server.ts instead. What you give up is the guarantee that what you deploy is what you checked: a stray enum or a missing extension then fails at request time rather than at build time. Ship dist for anything a customer touches; run .ts directly for scripts and internal tools.