
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。
▲ 使用 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 的資料。
▲ 當有 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);
})
▲ 當有 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);
})
▲ 在 postman 中新增 authorization 的欄位,並選擇 Bearer Token
▲ 可以用 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。
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 中。
// server.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.');
}
}
檔案結構:
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(第二版)