
從
pnpm init到pnpm start跑起來,一篇搞定 Express + TypeScript 專案初始化。適合想快速起手但不想踩坑的你。
先講結論
Express + TypeScript 的專案初始化其實不難,但有幾個地方很容易卡住:tsconfig.json 的 rootDir / outDir 沒設好會編譯到奇怪的地方、express-generator 生成的是 JS 檔要自己轉 TS、環境變數要記得裝 dotenv。這篇把整個流程走一遍,你照著做就能跑。
初始化專案 + 裝套件
mkdir express_ts_proto && cd express_ts_proto
pnpm init然後一口氣把需要的東西裝起來:
# 正式依賴
pnpm add express cookie-parser morgan dotenv
# 開發依賴(TypeScript + 型別定義)
pnpm add -D typescript ts-node @types/node @types/express \
@types/cookie-parser @types/morgan @types/debug為什麼用 pnpm?因為它用 hard link 共享套件,裝過一次的東西不會重複佔空間。你如果同時開好幾個 Node 專案,node_modules 不會把硬碟吃光。npm 表示不服,但硬碟表示很服。
別忘了加 .gitignore,至少把 node_modules/、dist/、.env 丟進去。
設定 TypeScript
pnpm exec tsc --init這會生出一個超長的 tsconfig.json,裡面大部分選項都是註解掉的。你真正需要改的就這幾個:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true
},
"include": ["src/**/*"]
}esModuleInterop 這個一定要開。不開的話 import express from 'express' 會報錯,你得寫成 import * as express from 'express',醜到不行。
用 express-generator 生骨架,然後改成 TS
pnpm add express-generator --save-dev
pnpm exec express --no-view這會生出一堆 .js 檔。接下來要做的事情就是:搬到 src/ 底下,副檔名改成 .ts,然後加上型別。最終的檔案結構長這樣:
/src
/bin
www.ts
/routes
index.ts
users.ts
app.ts
.env
核心檔案
src/app.ts — 應用的進入點,掛 middleware 和路由:
import express from 'express';
import cookieParser from 'cookie-parser';
import logger from 'morgan';
import routes from './routes';
const app = express();
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(routes);
export default app;src/bin/www.ts — 啟動伺服器,處理 port 衝突等錯誤:
#!/usr/bin/env node
import 'dotenv/config';
import app from '../app';
import http from 'http';
import debug from 'debug';
const normalizePort = (val: string) => {
const port = parseInt(val, 10);
if (isNaN(port)) return val;
if (port >= 0) return port;
return false;
};
const port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
const server = http.createServer(app);
server.on('error', (error: NodeJS.ErrnoException) => {
if (error.syscall !== 'listen') throw error;
const bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port;
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
default:
throw error;
}
});
server.on('listening', () => {
const addr = server.address();
const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr?.port;
debug('Listening on ' + bind);
});
server.listen(port);路由就很簡單了,src/routes/index.ts 當路由的集中管理處:
import { Router, Request, Response } from 'express';
import usersRouter from './users';
const router = Router();
router.get('/', (req: Request, res: Response) => {
res.send('Welcome to the Home Page');
});
router.use('/users', usersRouter);
export default router;src/routes/users.ts 同理,就不重複貼了。
跑起來
在 .env 加上:
PORT=3000
package.json 加上 start script:
"scripts": {
"start": "ts-node ./src/bin/www"
}然後 pnpm start,打開瀏覽器連 http://localhost:3000,看到 “Welcome to the Home Page” 就代表搞定了。
接下來你大概會想加 ESLint、型別定義管理、還有測試 — 那些我放在 下一篇:ESLint + Typings 設定 了。
專案初始化就像搬新家:東西先搬進去能住就好,裝潢的事明天再說。