Node.js 构建简单应用

把路由、静态文件、模板、表单拼成一个能跑的待办应用。

Intermediate
Key Points一个 Web 应用的最小闭环:路由分发 → 数据读写 → 页面渲染 → 表单提交。

项目结构

Text
todo-app/
├── package.json
├── server.js
├── store.js          ← 内存存储,之后可换数据库
├── views/
│   └── index.html
└── public/
    └── style.css

数据层

JavaScript
// store.js
let seq = 0;
const items = [];

module.exports = {
  list: () => items.slice(),
  add(text) {
    const item = { id: ++seq, text, done: false };
    items.push(item);
    return item;
  },
  toggle(id) {
    const it = items.find((i) => i.id === Number(id));
    if (it) it.done = !it.done;
    return it;
  },
  remove(id) {
    const i = items.findIndex((x) => x.id === Number(id));
    if (i === -1) return false;
    items.splice(i, 1);
    return true;
  },
};

服务层

JavaScript
// server.js
const express = require('express');
const store = require('./store');

const app = express();
app.use(express.urlencoded({ extended: false }));
app.use(express.static('public'));

app.get('/', (req, res) => {
  const items = store.list();
  const rows = items
    .map(
      (i) => `<li class="${i.done ? 'done' : ''}">
        <form method="post" action="/toggle/${i.id}" style="display:inline">
          <button>${i.done ? '✓' : '○'}</button>
        </form>
        ${i.text}
        <form method="post" action="/delete/${i.id}" style="display:inline">
          <button>×</button>
        </form>
      </li>`
    )
    .join('');
  res.send(`<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>待办</title>
<link rel="stylesheet" href="/style.css"></head>
<body>
  <h1>待办事项</h1>
  <form method="post" action="/add">
    <input name="text" required placeholder="要做什么">
    <button>添加</button>
  </form>
  <ul>${rows || '<li>还没有事项</li>'}</ul>
</body></html>`);
});

app.post('/add', (req, res) => {
  const text = (req.body.text || '').trim();
  if (text) store.add(text);
  res.redirect(303, '/');
});

app.post('/toggle/:id', (req, res) => {
  store.toggle(req.params.id);
  res.redirect(303, '/');
});

app.post('/delete/:id', (req, res) => {
  store.remove(req.params.id);
  res.redirect(303, '/');
});

app.listen(3000, () => console.log('http://127.0.0.1:3000'));

跑起来

Bash
npm init -y
npm install express
node server.js
Output
http://127.0.0.1:3000

Open the browser, type "buy milk" and submit: the page reloads with the item. Click ○ to mark done, × to delete. Zero client-side JS — it is all form posts and server-side re-rendering.

下一步

ImprovementHow
数据持久化把 store.js 换成 SQLite 或 JSON 文件
模板分离用 EJS / Handlebars,别在 JS 里拼 HTML
表单校验服务端校验必填和长度,前端只做体验
防重复提交PRG 模式(Post-Redirect-Get),本项目已用
res.redirect(303, '/') rather than 302 — 303 explicitly tells the client to switch to GET, which is the PRG standard. With 302 some clients re-post.

Friends & Partners