Open-Source Wikis

/

Express

/

Features

/

Templating

expressjs/express

Templating

Active contributors: dougwilson, wesleytodd, ulisesgascon

Express has a simple, engine-agnostic view system. The framework owns file lookup and locals merging; the template engine owns parsing and rendering.

The pieces

Piece Where
app.engine(ext, fn) lib/application.js
app.render(name, opts, cb) lib/application.js
res.render(name, opts, cb) lib/response.js
View(name, opts) lib/view.js
Template engine External package (ejs, hbs, etc.)

Configuration

app.set('views', path.join(__dirname, 'views')); // root directory(ies)
app.set('view engine', 'ejs'); // default extension
app.set('view cache', true); // automatically true in production

'views' accepts a string or an array of strings. View.lookup iterates them in order and returns the first match.

Registering a non-default engine

If a template engine exposes __express, it loads automatically when you reference its extension:

res.render('email.ejs', { name: 'Tobi' });

For engines without __express (or to bind a different file extension), use app.engine:

app.engine('html', require('ejs').renderFile);
res.render('page.html', { ... });

Render flow

sequenceDiagram
    participant Handler
    participant Res as res.render
    participant App as req.app.render
    participant View as View
    participant Engine as template engine

    Handler->>Res: res.render('home', { user })
    Res->>Res: opts._locals = res.locals
    Res->>App: app.render('home', opts, cb)
    App->>App: merge app.locals + opts._locals + opts
    App->>App: cache lookup if view cache enabled
    App->>View: new View('home', { defaultEngine, root, engines })
    View->>View: lookup file via root and resolve
    App->>View: view.render(opts, cb)
    View->>Engine: engine(path, opts, cb)
    Engine-->>View: html
    View-->>App: process.nextTick(cb, html)
    App-->>Res: cb(err, html)
    Res->>Handler: res.send(html) or call user cb

res.render defaults its own callback to res.send(str) if you don't pass one. If you do pass a callback, Express assumes you'll handle the response yourself.

Locals

There are two locals containers, both passed into the engine:

  • app.locals — application-wide. Set once at startup (or whenever).
  • res.locals — per-request. Typically populated by middleware (e.g., authentication info).

app.render merges them in this order, with later wins:

var renderOptions = { ...this.locals, ...opts._locals, ...opts };

opts._locals is automatically populated by res.render from res.locals. As of #6903 (in master post-5.2.1), app.render accepts null or undefined for opts correctly.

Caching

When 'view cache' is true, resolved View instances are stored in app.cache[name]. app.init defines app.cache = Object.create(null). Production (NODE_ENV === 'production') automatically enables view cache; other environments do not.

A custom render call can explicitly opt in or out via opts.cache.

Common patterns

Multiple views directories

app.set('views', [
  path.join(__dirname, 'views'),
  path.join(__dirname, 'modules/admin/views'),
]);

View.lookup checks each root in order.

Layouts and partials

Express does not have a built-in layout system. Use an engine that supports them (Pug, EJS with include, Handlebars with the hbs adapter, etc.). The framework only knows how to find and render a single file.

Streaming the rendered string

res.render always passes the rendered string to res.send, which buffers in memory. To stream, render via app.render(name, opts, (err, html) => { ... }) and pipe yourself, or use a streaming engine.

  • View for the implementation details of file resolution.
  • Application for app.engine, app.render, and the view cache setting.
  • Response for res.render.
  • Glossary for "engine", "view", "view cache", "locals".

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Templating – Express wiki | Factory