Comparison

Checking JavaScript in Node.js, Deno and Bun

What each runtime checks before and while it runs your JavaScript, which config each reads, and the commands, with one sample file checked by tsc.

  • Examples checked with TypeScript 5.9

At a glance

Last verified 21 October 2026 with TypeScript 5.9.3 (the sample file); runtime behavior per the Node.js, Deno and Bun documentation, none of them run here.

Compared Node.jsDenoBun
Runs .js with JSDoc Yes, as writtenYes, as writtenYes, as written
Checks types while running NoNo, by default since 1.23; deno run --check opts inNo; bun run --check opts in (1.4.3)
Built-in checker None: install typescript and run tscdeno check, with TypeScript's checker built inbun check (1.4.3), which its release notes describe as a port of TypeScript 7's checker
Checks .js files when tsc: checkJs, or // @ts-check per filecheckJs, // @ts-check, or deno check --check-jscheckJs is on
Config it reads None; tsc reads the jsconfig.json you namedeno.json, or a jsconfig.json or tsconfig.json beside a package.json (2.1 and later)tsconfig.json; its documentation does not mention jsconfig.json
Types for the runtime's own APIs @types/nodeBuilt in (the Deno namespace)@types/bun
Runs .ts files Yes, by erasing types (on by default from 22.18 and 23.6)YesYes

The same file in three runtimes

The file below uses only web APIs (Request, Response, URL), which all three runtimes provide, so the same code runs on each. Its types are JSDoc, so it runs as written everywhere; the question is only which checker reads the comments. This site checks it with tsc 5.9, and the second version shows the mistake every checker reports:

handler.js TypeScript 5.9.3 · checked 21 October 2026
/** @typedef {{ path: string, method: string }} Echo */

/**
 * Answers every request with its path and method, as JSON.
 * @param {Request} request
 * @returns {Response}
 */
export function handle(request) {
  const url = new URL(request.url);
  /** @type {Echo} */
  const body = { path: url.pathname, method: request.method };
  return Response.json(body);
}
A request handler that uses only web APIs.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["**/*.js"]
}
handler.js Error confirmed · TypeScript 5.9.3 · checked 21 October 2026
/** @typedef {{ path: string, method: string }} Echo */

/**
 * Answers every request with its path and method, as JSON.
 * @param {Request} request
 * @returns {Response}
 */
export function handle(request) {
  const url = new URL(request.url);
  /** @type {Echo} */
  const body = { path: url.pathnme, method: request.method };error TS2551: Property 'pathnme' does not exist on type 'URL'. Did you mean 'pathname'?
  return Response.json(body);
}
The same handler with a typo that no runtime reports when it runs.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["**/*.js"]
}

None of the three runtimes reports that typo when it runs the file: per their documentation, Node.js does not check types at all, and Deno and Bun check only when asked. The program starts, and fails on the first request.

Node.js

Node.js runs JavaScript and, since 22.18 and 23.6, .ts files with their types erased (JSDoc vs Node’s type stripping). It checks neither. The check is tsc, installed as a development dependency and run with your config:

npm install --save-dev typescript @types/node
npx tsc -p jsconfig.json

The Node.js setup has the config, including types: ["node"] for @types/node.

Deno

Deno ships TypeScript’s checker. Per its documentation, deno run stopped checking by default in 1.23 (deno run --check turns it back on), and deno check checks without running. JavaScript files are checked only when you ask: checkJs in the config, // @ts-check in the file, or deno check --check-js:

deno check --check-js main.js

Since 2.1, Deno reads a jsconfig.json or tsconfig.json when the project has a package.json, so the same config can serve tsc and Deno; a deno.json with compilerOptions takes precedence when both exist. The Deno setup covers both. Deno’s own APIs (Deno.serve) are typed by deno check only: tsc does not know them.

Bun

Per Bun’s documentation, Bun runs JavaScript and TypeScript without checking types. Since Bun 1.4.3 (10 October 2026), bun check checks a project from its tsconfig.json and writes nothing, and bun run --check checks before running; the release post describes the checker as a port of TypeScript 7’s. It reports errors in JavaScript files when checkJs is on, and its documentation does not mention jsconfig.json:

bun check

tsc works on Bun projects too, with @types/bun for Bun’s APIs; the Bun setup checks its example that way. We have not run bun check.

One checker or three

Every runtime here leaves type errors to a separate step, so the practical question is which checker your CI runs. Running tsc everywhere gives one TypeScript version, pinned in package.json, for the editor, CI and every developer. The runtimes’ own checkers save an install, at the cost of following their own release schedule for TypeScript: Deno’s built-in version, and Bun’s port of TypeScript 7, can differ from the typescript your editor runs.

Which to choose

Choose tsc, whatever the runtime when…

  • You want one checker and one config for code that runs in more than one place, or in CI on any machine.
  • You want the TypeScript version pinned in package.json, so the editor, CI and every developer get the same results.

Choose the runtime's own checker when…

  • The project runs only on Deno, where deno check needs no install and knows the Deno APIs.
  • The project runs only on Bun and you are happy with bun check following TypeScript 7, with a tsconfig.json.

Questions

Does any runtime refuse to run code with type errors?

Not by default. Per their documentation, Node.js never checks types, and Deno and Bun run without checking unless you pass --check. A type error stops nothing until a checker runs, which is why the check belongs in CI.

Can one jsconfig.json serve all three?

For tsc and Deno, yes: Deno reads a jsconfig.json when the project has a package.json. For bun check, name the file tsconfig.json, or keep running tsc on Bun projects, which works with @types/bun.

Which TypeScript does deno check use?

The one built into that Deno release. deno --version prints it. It may differ from the typescript in your package.json, so tsc and deno check can disagree after a TypeScript release.

Sources

Last reviewed 21 October 2026.