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 cbres.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.
Related pages
- View for the implementation details of file resolution.
- Application for
app.engine,app.render, and theview cachesetting. - 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.