首页 / Node.js 教程 / 模板引擎与静态文件

Node.js 教程

模板引擎与静态文件

本教程共 76 篇 · 第 41 篇 · 更新于 2026-07-25 · 约 5 分钟阅读

Node.jsExpress模板引擎EJS静态文件

41. 模板引擎与静态文件

本节目标:EJS 模板渲染、express.static 静态服务和 multer 文件上传。

不是每个接口都要返回 JSON。管理后台、落地页、邮件模板,这些场景需要服务端渲染 HTML。Express 支持多种模板引擎,配合 express.static 托管静态资源,能搭出完整的前后端不分离项目。

这一节我们讲 EJS(Embedded JavaScript),它语法简单、与 Express 集成最成熟。同时也讲静态文件托管和文件上传,这三件事构成了传统 Web 页面的基础能力。

配置模板引擎

先安装 EJS:

npm install ejs

Express 不需要显式 require('ejs'),只需在 app.js 里指定视图目录和引擎:

import express from 'express'
import path from 'path'

const app = express()

app.set('views', path.join(process.cwd(), 'views'))
app.set('view engine', 'ejs')

然后在 views/ 目录下放模板文件,扩展名是 .ejs。写一个简单的首页:

views/index.ejs

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title><%= title %></title>
</head>
<body>
  <h1><%= title %></h1>
  <p><%= message %></p>

  <ul>
    <% items.forEach(item => { %>
      <li><%= item %></li>
    <% }) %>
  </ul>
</body>
</html>

路由里用 res.render() 渲染:

app.get('/', (req, res) => {
  res.render('index', {
    title: 'Express + EJS',
    message: 'Hello from server-side rendering',
    items: ['Node.js', 'Express', 'EJS']
  })
})

res.render('index', data) 会做三件事:找到 views/index.ejs,把数据填进去,将生成的 HTML 作为响应发送。Content-Type 自动设为 text/html

EJS 语法速查

EJS 标签有三种,别搞混:

标签作用示例
<%= value %>输出值(HTML 转义)<%= user.name %>
<%- value %>输出原始 HTML(不转义)<%- htmlContent %>
<% code %>执行 JS 语句,不输出<% if (x) { %>
Warning

<%- %> 不转义 HTML,如果输出的是用户输入的内容,会导致 XSS 漏洞。只有当你确实要插入可信的 HTML(比如富文本编辑器生成的内容)时才用它,否则一律用 <%= %>

条件渲染和循环都是普通 JavaScript:

<% if (user) { %>
  <p>Welcome, <%= user.name %></p>
<% } else { %>
  <p>Please <a href="/login">login</a></p>
<% } %>

<% products.forEach(p => { %>
  <div class="product">
    <h3><%= p.name %></h3>
    <span<%= p.price %></span>
  </div>
<% }) %>

EJS 支持局部模板(partials),用来抽取公共片段:

<!-- views/partials/head.ejs -->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/css/style.css">

在页面里引入:

<head>
  <title><%= title %></title>
  <%- include('partials/head') %>
</head>

静态文件托管

CSS、JS、图片这些不常变的资源,用 express.static 直接映射到目录:

app.use(express.static('public'))

假设目录结构:

public/
├── css/
│   └── style.css
├── js/
│   └── app.js
└── images/
    └── logo.png

前端就可以通过 /css/style.css/images/logo.png 访问。注意路径里不带 publicexpress.static 已经把虚拟根目录指向了 public/ 内部。

如果你想加个前缀,比如所有静态资源走 /assets

app.use('/assets', express.static('public'))

此时访问路径变成 /assets/css/style.css。生产环境建议用绝对路径:

app.use('/assets', express.static(path.join(process.cwd(), 'public')))
Tip

express.static 内部会根据文件扩展名自动推断 Content-Type,并且默认开启缓存。如果要调整缓存策略,可以传选项:
app.use(express.static('public', { maxAge: '1d' }))

文件上传:multer

表单上传文件时,enctype 必须是 multipart/form-data,这种格式 express.json()express.urlencoded() 解析不了,需要专门的中间件。最常用的是 multer

npm install multer

基础用法:

import multer from 'multer'

const upload = multer({ dest: 'uploads/' })

// 单文件上传,字段名 "avatar"
app.post('/upload', upload.single('avatar'), (req, res) => {
  console.log(req.file)   // 文件信息
  res.json({
    filename: req.file.originalname,
    size: req.file.size,
    path: req.file.path
  })
})

req.file 的结构:

{
  fieldname: 'avatar',
  originalname: 'photo.jpg',
  encoding: '7bit',
  mimetype: 'image/jpeg',
  destination: 'uploads/',
  filename: 'a1b2c3d4e5f6...',
  path: 'uploads/a1b2c3d4e5f6...',
  size: 12345
}

multer 默认把文件名改成随机哈希,防止重名覆盖。如果你希望保留原始文件名,自定义存储引擎:

const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'uploads/')
  },
  filename: (req, file, cb) => {
    const unique = Date.now() + '-' + Math.round(Math.random() * 1e9)
    cb(null, unique + '-' + file.originalname)
  }
})

const upload = multer({ storage })

多文件上传用 upload.array('photos', 5),最多允许 5 张。混合字段用 upload.fields([{ name: 'avatar', maxCount: 1 }, { name: 'gallery', maxCount: 8 }])

Warning

上传功能最容易出安全问题。multer 不会校验文件类型,只信任前端传来的 mimetype,而 mimetype 可以被伪造。生产环境务必做后缀白名单校验,并且不要把上传目录放在 public 下直接暴露给用户访问。

模板引擎与前后端分离

EJS 这类服务端渲染适合管理后台、SEO 页面、邮件模板。如果是重交互的 C 端应用,前后端分离(React/Vue 做前端,Express 纯当 API 网关)是更主流的选择。

Express 并不强制你用哪种方案。需要 SSR 就配 EJS/Pug,需要 API 就 res.json(),两者甚至可以共存——同一个项目里,管理后台走模板渲染,App 接口走 JSON。