大家好,我是 Yubin
這篇文章會介紹如何將 Fastify 作為後端,並整合前端網頁,由 Fastify App 作為前端網頁的 Host,實現 Full Stack 的專案。
前端使用 React,後端使用 Fastify 搭配
@fastify/static為範例。
React 是一個 JavaScript 的函式庫,透過 React 我們可以輕鬆的設計出複雜的網頁畫面。
關於 React 的學習,可以參考官方的教學文件 或其他人的鐵人賽文章 (很多)。
我們可以透過 create-reat-app 來快速建立一個 React 專案。
這邊打算把前後端的程式放在同一個專案目錄底下,用
frontend表示前端、backend表示後端。
mkdir my-app
cd my-app
npx create-react-app frontend --template typescript
使用 --template typescript 選擇 TypeScript 的樣板。
等專案建立好,敲入以下指令就可以看到這個樣板預設的畫面:
cd frontend
npm start

會自動打開預設的瀏覽器,瀏覽 localhost:3000,3000 是 React 開發伺服器預設使用的 Port。
這邊必須要知道的是,透過 create-react-app 產生的專案,會使用 react-scripts 做為主要的工具,其中有三個重要的 scripts 分別對應三種模式。
npm run start
開發模式,會把 React App 開起來,預設會聽在 localhost:3000,在這個模式下,如果有改動程式碼,畫面會自動更新,有錯誤或警告訊息也會在 Console 看到。
同時會把 NODE_ENV 環境變數設為 development。
npm run build
生產模式,會把 React App 最佳化並打包成一些靜態檔案,出來的靜態檔案會放在 build  目錄底下。此時的 app 是可以準備部屬上線的。
打包過程中,會把 NODE_ENV 環境變數設為 production。
npm run test
測試模式,會執行測試檔案。
測試過程中,會把 NODE_ENV 環境變數測為 test。
這邊假設我們已經把前端畫面開發完畢,敲入 npm run build。

可以看到產生出來的 build 目錄,觀察裡面可以看到 index.html 就是網頁的進入點,然後各個 React Compoment 都被最佳化並打包成許多 JavaScript, CSS 檔放在 static 目錄中。
到此,我們只需要把 build 目錄,交給一個網頁伺服器,就可以讓網站上線了。
但本篇的主題是前後端整合,我們會使用 Fastify 當作我們的前端網頁的 Host。
在專案目錄中,建立 backend 目錄,用來放後端的程式。
cd my-app
mkdir backend
cd backend
npm init -y
這邊快速的建立一個基本的 Fastify App,還不熟 Fastify 的基本專案怎麼實作的朋友,可以參考 Fastify101: Hello World。
npm i fastify
npm i -D typescript @types/node
npx tsc --init
修改 tsconfig.json:
"include": ["src/**/*.ts"],
"exclude": ["node_modules"],
"compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src",
}
新增 src/server.ts:
import fastify, { FastifyInstance } from 'fastify'
const server: FastifyInstance = fastify()
const startServer: (port: number) => FastifyInstance = (port) => {
  const listenAddress = '0.0.0.0'
  const fastifyConfig = {
    port: port,
    host: listenAddress
  }
  server.listen(fastifyConfig, (error, _) => {
    if (error) {
      console.error(error)
    }
  })
  server.get('/hello', async (request, reply) => {
    return reply.status(200).send({
      message: 'Hello World'
    })
  })
  return server
}
export { startServer }
新增程式進入點 src/index.ts:
import { startServer } from './server'
const port = 8888
startServer(port)
修改 package.json 中的 scripts:
"scripts": {
    "build": "tsc",
    "start": "node dist/index.js"
},
以上完成一個基本的 Fastify 專案架構,可以編譯並執行:
npm run build && npm run start
打開瀏覽器,瀏覽 localhost:8888/hello。

看到如我們定義的,顯示 Hello World 回應。
一個基本的 Fastify App 已經好了,我們的前端也 build 出準備部屬的靜態檔案了,現在要讓 Fastify App 可以 serve 這些靜態檔案。
講人話,就是別人發送 Request 給伺服器想看網頁,伺服器可以回應
.html,.js,.css,.jpg等靜態檔案出去。
這邊可以透過 Fastify 官方的 @fastify/static 來實現。
@fastify/static 是一個可以快速回應靜態檔案的 Fastify Plugin。
可以透過 npm 來安裝:
npm i @fastify/static
安裝好後透過 server.register() 註冊:
import fastifyStatic from '@fastify/static'
import path from 'path'
server.register(fastifyStatic, {
    root: path.join(__dirname, '../../frontend/build'),
    prefix: '/'
})
註冊的時候可以帶入一些參數。
root,必要的參數,要指定要 serve 的目錄的絕對路徑。上述範例透過 path.join 及 __dirname 來指到 frontend 專案的 build 目錄。
prefix,用來當 url 的前墜,預設為 /。
更多參數可以參考官方的文件。
接著再次編譯並把後端專案起起來:
npm run build && npm run start
打開瀏覽器,瀏覽 localhost:8888。

可以看到,原本我們的後端在 / 這個 Endpoint 沒有定義東西,應該要呈現 404 的回應,但我們透過 @fastify/static 這個 plugin 把前端網頁的靜態檔案 serve 起來,prefix 設定為 /。所以可以瀏覽到前端的畫面。
本文的做法,是將前端專案 build 出靜態檔案,把那些檔案交由後端的伺服器來 Serve。
注意前端專案要先 build 完,產生要部屬的靜態檔案才有畫面。
部屬方面相對單純,因為只會有後端的伺服器,別人要看網頁或 API 資源都只要跟這個伺服器作互動就好。
在前端專案的程式碼中,如果要拿取後端的 API 資源,也只要透過相對路徑的方式就可以存取到。
因為前端最後會跟後端住在一起。
假設後端有一個 API 開在 GET /hello 上。
前端的程式可以像這樣用相對路徑的方式拿到資源,而不用管後端的 hostname 是什麼:
// 以 axios 為例
const response = await axios.get('/hello')
因為最後他們是住在一起的,是同個 server 上的東西。
本文介紹了利用 @fastify/static 來實現 Full Stack 專案的前後端整合架構。
本篇以 React 專案做解釋,但這個方法不限於 React 專案,只要最後 build 出來的產物是靜態檔案的都可以用這個方式整合。
完整範例程式可以參考 GitHub。