cover

Express + TypeScript 專案從零開始的步驟

Express + TypeScript 專案從零開始的步驟

前置說明

本文將教你如何使用 pnpm 來從零開始建立一個 Express + TypeScript 的專案。使用 pnpm 的好處在於它提供了快速、節省磁碟空間的套件管理方式,並且非常適合大型專案的依賴管理。接下來,我們會一步步完成專案的初始化、基本架構的搭建,以及一些 Express 與 TypeScript 的最佳實踐。

1. 建立專案目錄

首先,進入到你想要創建專案的目錄並初始化專案:

mkdir express_ts_proto
cd express_ts_proto
pnpm init

pnpm init 會初始化專案並生成 package.json 文件,這將作為專案的基礎設定檔。

還有要新增 .gitignore 檔案,把常見的檔案都 ignore 掉。

2. 安裝 Express 和相關依賴

使用 pnpm 安裝 Express 和相關的依賴:

pnpm add express cookie-parser morgan

這個指令會安裝 Express 框架以及常用的中介軟體,例如處理 cookie 的 cookie-parser 和記錄 HTTP 請求的 morgan

3. 安裝 TypeScript 和相關開發依賴

接著,安裝 TypeScript 及相關的型別定義檔案,讓專案支援 TypeScript:

pnpm add -D typescript @types/node @types/express @types/cookie-parser @types/morgan ts-node @types/debug

這些開發依賴會幫助專案在開發階段支援 TypeScript 型別檢查。

4. 初始化 TypeScript 配置

創建一個 tsconfig.json 文件來設置 TypeScript 編譯器的配置:

pnpm exec tsc --init

修改 tsconfig.json 文件如下,來配置輸出和根目錄:

{
    "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true
    },
    "include": ["src/**/*"]
}

這樣的配置可以確保 TypeScript 會將編譯後的 JavaScript 文件輸出到 dist 目錄,並設置專案的源文件路徑為 src

5. 建立 Express 應用模板

使用 express-generator 來生成基本的 Express 應用結構,並指定不使用視圖引擎:

pnpm add express-generator --save-dev
 
pnpm exec express --no-view

這將會建立一個預設的應用結構,隨後我們會將其轉換為 TypeScript 格式。

6. 安裝 dotenv 用來管理環境變數

安裝 dotenv 來處理 .env 環境變數文件:

pnpm add dotenv

7. 建立 .env 文件

在專案根目錄創建 .env 文件來定義環境變數:

PORT=3000
DEBUG=*

這裡設定了應用的預設埠和調試模式。

8. 建立應用文件結構

新增 src/ 目錄並且在 src/ 下建立以下文件結構:

/src
    /bin
        www.ts
    /routes
        index.ts
        users.ts
    app.ts

9. 編寫 src/app.ts

請將原本的 app.js 重新命名修改為 app.ts,並且確認搬移到 src/ 資料夾裡面。 app.ts 是應用的核心,負責設置中介軟體(Middleware)和路由。

import express from 'express';
import { join } from 'path';
import cookieParser from 'cookie-parser';
import logger from 'morgan';
import routes from './routes';
 
const app = express();
 
// Middleware
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
 
// Uncomment if needed for serving static files
// app.use(express.static(join(__dirname, 'public')));
 
// Use centralized routes
app.use(routes);
 
export default app;

10. 編寫 src/bin/www.ts

請講原本的 bin/www 移動並且修改副檔案名 src/bin/www.tswww.ts 負責啟動伺服器並處理錯誤。

#!/usr/bin/env node
 
import 'dotenv/config';  // Load .env variables
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 onError = (error: any) => {
  if (error.syscall !== 'listen') {
    throw error;
  }
 
  const bind = typeof port === 'string'
    ? 'Pipe ' + port
    : 'Port ' + port;
 
  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges');
      process.exit(1);
      break;
    case 'EADDRINUSE':
      console.error(bind + ' is already in use');
      process.exit(1);
      break;
    default:
      throw error;
  }
};
 
const onListening = () => {
  const addr = server.address();
  if (addr !== null && typeof addr !== 'string') {
    const bind = 'port ' + addr.port;
    debug('Listening on ' + bind);
  } else if (addr !== null && typeof addr === 'string') {
    const bind = 'pipe ' + addr;
    debug('Listening on ' + bind);
  } else {
    console.error('Server address is null.');
  }
};
 
const port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
 
const server = http.createServer(app);
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

11. 建立 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;

12. 建立 src/routes/users.ts

users.ts 負責管理 /users 路由邏輯。

import { Router, Request, Response } from 'express';
 
const router = Router();
 
/* GET users listing. */
router.get('/', (req: Request, res: Response) => {
    res.send('respond with a resource');
});
 
export default router;

13. 更新 package.json

package.json 中添加 start 腳本來啟動應用:

"scripts": {
    "start": "ts-node ./src/bin/www"
}

14. 運行專案

現在,應用可以通過 pnpm start 命令啟動:

pnpm start

如果一切順利,你應該會看到伺服器成功啟動並開始監聽指定的端口。


15. 最佳實踐與未來發展

  • 環境變數管理:通過 .env 文件靈活管理專案的環境設置。
  • TypeScript 錯誤處理:確保每個文件中的參數、返回值都有正確的型別註解。
  • 中介軟體與路由的拆分:未來可以將中介軟體和路由拆分成更多模組以便

16. 參考連結

參考連結