TypeScript Express

【 Node.js 】 用 TypeScript 和 Express 建立一個 http server

1. 建立 TypeScript 的環境

首先要先建立 TypeScript 的環境,請參考:如何在 node.js 中建立 TypeScript 的環境

2. 使用 http module create http server

建立完 TypeScript 的環境後,引入 Node.js 的核心套件之一:http module。 http module 能讓你建立一個 http server,當你的 code 在執行的時候,可以隨時監聽網路上發送到你 url 的所有 request,並將你寫的程式邏輯 response 回去。

Import http module 其實跟 JavaScript 的寫法差不多,只是在 request 和 response 加上對應的型別:IncomingMessage, ServerResponse。

import { createServer, IncomingMessage, ServerResponse } from 'http';
 
const port = 5000;
 
const server = createServer((request: IncomingMessage, response: ServerResponse) => {

  response.on('error', (err) => {
    console.error(err);
  });
  response.writeHead(200, {"Content-Type": "text/plain"});
  response.end('Hello world!');
});
 
server.listen(port);
console.log(`server is running on http://localhost:5000`)

如此一來每當有任何 client 對 http://localhost:5000 這個 url 發送 request 時,我們的 server 都會回應 ‘Hello world!’ 給該 client。

TypeScript Express
▲ 使用 Postman 發送 request 的結果

3. 取得 request 中的資料

3.1 取得 request 中的 body

大部分的時候,如果我們需要傳資料到 server ,會發送 POST request ,並且將資料存在 body 當中,要在 Node.js 取得 request 中的 body data 方法如下:

let body = [];
request
  .on('error', (err) => {
    console.error(err);
  })
  .on('data', (chunk) => {
    body.push(chunk);
  })
  .on('end', () => {
    body = Buffer.concat(body).toString();
    console.log(body);
  })

如果直接將 chunk console.log 出來會發現是一段看不懂的資料:`<Buffer 7b 0d 0a 20 20 20 20 22 74 65 73 74 22 3a 20 22 35 22 0d 0a 7d>`,這是因為 http request 在發送給 server 的時候是按照順序一個 byte 一個 byte 以資料流的方式發送的,因此需要用 Buffer.toString() 轉換回原本的資料。

當我使用 POST method 發送 request 時,我的 server 端就會印出 request 中 body 的資料。

TypeScript Express
▲ 當有 request 時,server 端會console 出 request 裡的 body 資料

3.2 取得 request 中的 header

直接用 request.headers 取得 request 中的基本資料:

request
  .on('error', (err) => {
    console.error(err);
  })
  .on('end', () => {
    console.log(request.headers);
  })
TypeScript Express
TypeScript Express
▲ 當有 request 時,server 端會 console 出 request 裡的 headers

3.3 取得 request 中的 authorization

authorization 是我們習慣放置 token 的欄位,雖然在 postman 中是獨立的一欄,但事實上是存在 request 的 headers 裡的。

request
  .on('error', (err) => {
    console.error(err);
  })
  .on('end', () => {
    console.log(request.headers);
    console.log(request.headers.authorization);
  })
TypeScript Express
▲ 在 postman 中新增 authorization 的欄位,並選擇 Bearer Token
TypeScript Express
▲ 可以用 request.headers.authorization 存取到 token

完整程式碼:

import { createServer, IncomingMessage, ServerResponse } from 'http';
 
const port = 5000;
 
const server = createServer((request: IncomingMessage, response: ServerResponse) => {
  const { headers, method, url } = request;
  let body:any = [];
  request
    .on('error', (err) => {
      console.error(err);
    })
    .on('data', (chunk) => {
      body.push(chunk);
    })
    .on('end', () => {
      body = Buffer.concat(body).toString();
      console.log(body);
      console.log(request.headers);
      console.log(request.headers.authorization);
    })
  response.on('error', (err) => {
    console.error(err);
  });
  response.writeHead(200, {"Content-Type": "text/plain"});
  response.end('Hello world!');
});
 
server.listen(port);
console.log(`server is running on http://localhost:5000`)

4. 新增 routes

大多時候,我們希望 client 對不同的 url 發送 request 時可以獲得不同的資料或頁面,這時候就需要新增 router 來管理。

import { createServer, IncomingMessage, ServerResponse } from 'http';
 
const port = 5000;
 
const server = createServer((request: IncomingMessage, response: ServerResponse) => {
  const { url } = request;
  switch(url) {
    case '/':
      response.writeHead(200, {"Content-Type": "text/plain"});
      response.write('Homepage');
      response.end();
      break
    case '/articles':
      response.writeHead(200, {"Content-Type": "text/plain"});
      response.write('All articles are here!');
      response.end();
      break
    case '/about-me':
      response.writeHead(200, {"Content-Type": "text/plain"});
      response.write('My name is Jimmy.');
      response.end();
      break
    default:
      response.writeHead(404, {"Content-Type": "text/plain"});
      response.write('Page not found.');
      response.end();
  }
});

使用 switch case 來根據不同 request 的 url 來回應相對應的 response。

TypeScript Express
TypeScript Express
TypeScript Express
TypeScript Express

5. 使用 Express 來改寫

import  express, { Express, Request, Response } from 'express';
 
const port = 5000;

const app: Express = express();

app.get('/', (request: Request, response: Response) => {
  response.type('text/plain');
  response.send('Homepage');
})

app.get('/articles', (request: Request, response: Response) => {
  response.type('text/plain');
  response.send('All articles are here!');
})

app.get('/about-me', (request: Request, response: Response) => {
  response.type('text/plain');
  response.send('My name is Jimmy.');
})

app.use((request: Request, response: Response) => {
  response.type('text/plain');
  response.status(404)
  response.send('Page is not found.');
})
 
app.listen(port, () => {
  console.log(`server is running on http://localhost:5000`)}
);

6. 將 routes 和 controllers 獨立出來

當專案越長越大後,將所有 routes 和邏輯都寫在 server.ts 是個很難維護的做法,因此大多數的專案都會將 routes 和 controllers 獨立出來,把商業邏輯寫在 controllers 中。

// srver.ts
import express, { Express, Request, Response, NextFunction } from 'express';
import { ApiRouter } from './routes/api-routes';
 
const port = 5000;

const app: Express = express();
const apiRouter = new ApiRouter;

app.use((request: Request, response: Response, next: NextFunction) => {
  response.setHeader("Access-Control-Allow-Origin", "*");
  response.setHeader(
    "Access-Control-Allow-Headers",
    "Origin, X-Requested-With, Content-Type, Accept, Authorization"
  );
  response.setHeader("Access-Control-Allow-Methods", "GET, POST, PATCH, DELETE");

  next();
});

app.use('', apiRouter.router)

app.use((request: Request, response: Response) => {
  response.type('text/plain');
  response.status(404)
  response.send('Page is not found.');
})
 
app.listen(port, () => {
  console.log(`server is running on http://localhost:5000`)}
);
// routes/api-routes.ts
import  express, { Router, Request, Response, NextFunction } from 'express';
import { ApiControllers } from '../controllers/api-controllers';

const apiControllers = new ApiControllers;

export class ApiRouter {
  router: Router;
  constructor() {
    this.router = express.Router();
    this.initializeRoutes();
  }

  initializeRoutes() {
    this.router.get('/', apiControllers.getHomePage);
    this.router.get('/articles', apiControllers.getArticlesPage);
    this.router.get('/about-me', apiControllers.getAboutPage)
  }
}
// controllers/api-controllers.ts
import { Request, Response, NextFunction } from 'express';

export class ApiControllers {
  getHomePage(request: Request, response: Response, next: NextFunction) {
    response.type('text/plain');
    response.send('Homepage');
  }

  getArticlesPage(request: Request, response: Response, next: NextFunction) {
    response.type('text/plain');
    response.send('All articles are here!');
  }

  getAboutPage(request: Request, response: Response, next: NextFunction) {
    response.type('text/plain');
    response.send('My name is Jimmy.');
  }
}

檔案結構:

TypeScript Express

7. 參考資料

文章:
TypeScript Express tutorial #1. Routing, controller, middleware
Node.js TypeScript #7. Creating a server and receiving requests
Node.js and TypeScript Tutorial: Build a CRUD API
Node.js 官方文件

書籍:
用Node.js一統JavaScript前後端:強勢Web開發親手作
網頁應用程式設計:使用 Node 和 Express(第二版)

如果覺得我的文章有幫助的話,歡迎幫我的粉專按讚哦~謝謝你!

Leave a Comment

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

Scroll to Top