Skip to content

fix(node): add the charset to a content type the app sets - #18391

Merged
kamilmysliwiec merged 1 commit into
nestjs:masterfrom
pavstev:fix/node-content-type-charset
Nov 4, 2026
Merged

kamilmysliwiec merged 1 commit into
nestjs:masterfrom
pavstev:fix/node-content-type-charset

Conversation

@pavstev

@pavstev pavstev commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Other... Please describe:

What is the current behavior?

The NodeAdapter sends a Content-Type as the app gives it. Express runs it through mime-types, so it adds ; charset=utf-8 to text/*, application/json and the other types that have a charset.

@Get()
@Header('Content-Type', 'text/html')
page() {
  return '<p>zażółć</p>';
}
adapter Content-Type sent
express text/html; charset=utf-8
node text/html

A browser then guesses the charset, and non-ASCII text can show up broken. The same happens with res.set(), res.header() and res.type('text/html') on the node response. res.set('Content-Type', [...]) is accepted, but Express throws a TypeError.

Issue Number: N/A

What is the new behavior?

NodeResponse.set() (and so header(), and type()) and NodeAdapter.setHeader() (the @Header() path, as in the ExpressAdapter) run a Content-Type through the same mime-types lookup that type() already used:

adapter Content-Type sent
express text/html; charset=utf-8
node text/html; charset=utf-8
  • A value with a charset stays as it is: text/html; charset=latin1.
  • A type with no charset in mime-db stays as it is: application/vnd.api+json, image/png.
  • An unknown value stays as it is (the || value fallback), so it is never turned into false.
  • An array as the Content-Type throws TypeError('Content-Type cannot be set to an Array'), as Express does.

Tests: node-request.spec.ts (set()/header() and type()), node-adapter.spec.ts (setHeader() then reply()), and a @Header('Content-Type', 'text/html') route in integration/http-adapters that runs for express and node. All new cases fail on master and pass with the fix. One old send() test expected text/html back for set('Content-Type', 'text/html'); it now expects the charset, as Express sends it.

Not in this PR: Express res.send(string) also adds the charset to a preset type that has none in mime-db (res.set('Content-Type', 'application/vnd.api+json').send('...') gives application/vnd.api+json; charset=utf-8). The node adapter keeps the type as it is. I left it out to keep this change small. I can send it as a follow-up if you want it.

Does this PR introduce a breaking change?

  • Yes
  • No

@nestjs/platform-node is new in 12.3. An app that sets text/html by hand now gets the charset, which is what Express sends too.

Other information

Found by comparing the NodeAdapter with Express 5.3 for the same @Header() route.

NodeResponse.set(), header(), type() and NodeAdapter.setHeader() (the
@Header() path) sent a Content-Type as it was given. Express runs it
through mime-types, so text/html is sent as text/html; charset=utf-8.
An array Content-Type now throws a TypeError, as it does in Express.
@kamilmysliwiec
kamilmysliwiec merged commit 3596f00 into nestjs:master Nov 4, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants