我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。
首先,什麼是多租戶SaaS應用程序?
多租戶SaaS應用程序可讓您從單個代碼庫中為多個客戶提供服務。但是,為此,您需要管理安全和特定於租戶的訪問,手動完成時可能會具有挑戰性。這就是為什麼我決定使用許可證,這是一種簡化此過程的現代授權工具的原因。
在本文中,我將向您展示如何使用許可證簡化SaaS應用程序的授權,並逐步構建具有私人隔離和基於角色的訪問控制(RBAC)的演示應用程序(RBAC),並使用Next.js和AppWrite。
什麼是下一步。 js和appwrite,為什麼我們需要它們?
next.js
Next.js是一個基於React的框架,可提供服務器端渲染(SSR),靜態站點生成(SSG),API路由和性能優化。
對於這個項目,我使用了Next.js,因為:
- 它允許預先渲染頁面,從而改善性能和SEO。
- 它的內置路由使管理頁面過渡和動態內容易於管理。
- 它可以輕鬆地與諸如AppWrite和Clum.io的後端服務集成以進行身份驗證和授權。
AppWrite
AppWrite是一個後端AS-A-Service(BAAS)平台,可提供用戶身份驗證,數據庫,存儲和無服務器功能。使用諸如AppWrite之類的服務消除了從頭開始構建後端的需求,因此您可以在訪問後端功能的同時專注於前端開發。
對於這個項目,我使用了AppWrite:
- 處理用戶註冊,登錄和會話管理。
- 提供一個結構化的NOSQL數據庫來存儲特定於租戶的數據。
使用Next.js和AppWrite一起,可以使我創建一個可擴展的高性能多租戶SaaS應用程序,同時保持開發過程有效。
多租戶SAAS授權簡介
多租戶SaaS應用程序是使用該應用程序的單個軟件實例為多個用戶或稱為租戶的用戶組的軟件。
這意味著在多租戶SaaS體系結構中,多個客戶(租戶)共享相同的應用程序基礎架構或使用相同的應用程序,但要維護數據隔離。
一個實踐的例子是Trello等項目管理工具。
- 它是一個單個基礎架構,可在共享服務器上運行,並為其所有用戶具有相同的代碼庫。
- 使用Trello(例如A和B公司B)的每個公司都是租戶。
- 它隔離數據:
- 公司A的員工只能看到他們的項目,任務和董事會。
- B公司的員工無法訪問或查看公司A的數據,反之亦然。
這樣可以確保在共享資源時,每個租戶的數據和活動都是私人且安全的。
在多租戶應用程序中,即使在租戶內,有些用戶也可以更高地訪問某些信息,而某些成員將僅限於某些資源。
在此類應用中的授權必須:
- 確保用戶無法訪問其他租戶的數據或資源。這稱為隔離租戶。
- 確保租戶內的用戶只能通過提供顆粒狀訪問控制來訪問其角色允許的資源。
- 處理更多的用戶,租戶和角色,而不會減慢或退化性能。
租戶隔離和顆粒狀訪問控制的重要性
租戶隔離通過確保每個客戶的信息保持私密來確保數據的安全。雖然粒狀訪問控制可確保組織內的用戶僅獲得所需的權限。
在SaaS應用程序中實施授權可能是複雜而棘手的,但是當您擁有像許可證這樣的授權工具時,它不必是。
什麼是許可,其好處是什麼?
許可證是一種易於使用的授權工具,用於管理任何應用程序中的訪問,包括多租戶應用程序。在您的應用程序中使用許可證。您可以輕鬆地定義和分配角色,並在應用程序中使用特定的權限進行訪問控制。除了在應用程序中創建角色外,您還可以根據用戶或資源屬性添加條件和規則,以指定每個用戶可以做什麼和不能做什麼。
現在,您知道了您需要了解的有關許可證及其福利的大部分內容,讓我們進入主要交易 - 與Next.js一起建立SaaS申請並集成授權許可證。
為了展示許可的力量,我們將建立一個多租戶Edtech SaaS平台。
構建Edtech SaaS平台涉及幾個挑戰,包括用戶身份驗證,基於角色的訪問控制(RBAC)和多租戶。我們將使用Next.js進行前端,AppWrite用於身份驗證和數據庫管理,並允許授權精細。
技術堆棧概述
系統體系結構
該應用程序遵循後端優先的方法:
- 後端(node.js express)
- 處理API請求和業務邏輯。
- 使用AppWrite進行身份驗證和數據庫管理。
- 工具允許授權,定義角色和權限。
- 確保在數據訪問之前驗證每個請求。
- 前端(Next.js)
- 連接到後端以安全地獲取數據。
- 使用基於角色的UI渲染,這意味著用戶只能看到他們授權訪問的內容。
- 根據權限限制操作(例如創建作業)。
通過在API級別執行授權,我們確保用戶無法繞過限制,即使他們操縱前端。
在本指南的末尾,您將擁有一個功能齊全的多租戶Edtech SaaS應用程序,其中:
- 管理員可以添加和查看學生。
- 教師可以添加和查看學生,並創建作業。
- 學生只能查看他們指定的課程。
本文提供了我如何實施許可來處理構建該項目的授權的分步分類,因此請跟隨並構建您的項目。
獲得許可證的後端實施
要執行基於角色的訪問控制(RBAC)和租戶隔離,我們需要:
- 設置許可證並定義角色,租戶和政策。
- 在後端(Node.js Express)集成許可證。
- 使用中間件在允許請求之前檢查權限。
讓我們逐步吧。
1。設置許可證
在編寫任何代碼之前,您需要
- 根據許可創建帳戶。

您將出現入職,但是一旦輸入組織名稱,就可以跳過設置。
- 創建資源和動作
導航到策略部分,您將創建一個可以在該資源上執行的資源和操作。

創建資源後,應該看起來像這樣:

- 創造角色
創建資源後,使用“角色”選項卡導航到角色頁面。您會發現某些角色已自動分配。

刪除這些角色並創建新角色。每個角色都將具有與用戶可以做什麼和不能做什麼相關的特定規則。首先創建管理員角色,因為它將作為RBAC條件的構建塊。單擊頂部的添加角色按鈕並創建角色。

創建角色後,應該看起來像這樣:

偉大的!
現在您已經創建了資源和角色,現在可以在策略編輯器中配置權限。
- 在策略編輯器中配置權限
返回策略編輯器,這就是角色現在的樣子,每個資源定義了每個資源以及您可以選擇的操作。現在,您可以在資源上執行所選操作的角色權限。

完成每個角色的操作後,請單擊頁面右下方的“保存更改”按鈕。
- 複製API鍵
最後,要使用許可證的雲PDP,您將需要當前環境的API鍵。對於此項目,您將使用開發環境密鑰。繼續進行設置,然後單擊API鍵,向下滾動到環境API鍵,單擊“顯示鍵”,然後復制它。

設置許可儀表板後,您現在可以繼續前進。
2。安裝依賴項
首先,您需要在計算機上安裝node.js。確保系統上安裝了node.js後,請按照以下步驟:
- 首先使用以下命令創建一個新項目:
Mkdir後端 CD Backendnpm Init -Y
- 然後,安裝以下軟件包:
NPM安裝Express Dotenv允許CORS APPWWRITE AXIOS JSONWEBTOKEN
- Express中的配置許可證。在您的.env文件中,存儲您的API密鑰:
pull_api_key =您的permit-key-you popied-earlier
3.設置AppWrite
- 轉到AppWrite並通過輸入項目名稱並選擇區域來創建一個新項目。記下您的項目ID和API端點;這就是您將作為.env文件中的值輸入的內容。您的env文件應該看起來像這樣:
pull_api_key =您的permit-key-you popied-earlier appwrite_endpoint = https://cloud.appwrite.io/v1 appwrite_project_id = your-project-id
- 現在,繼續使用數據庫以創建您的數據庫,然後復制數據庫ID將其粘貼到Env文件中。

您的env文件現在應該看起來像這樣:
pull_api_key =您的permit-key-you popied-earlier appwrite_endpoint = https://cloud.appwrite.io/v1 appwrite_project_id = your-project-id appwrite_database_id =您的數據庫-ID
現在,在AppWrite數據庫中創建以下屬性:
- 配置文件集合

- 學生收集

- 分配收集

目前,您的env文件應該是什麼樣子:
pull_api_key =您的permit-key-you popied-earlier pliper_project_id = copy-from-dashboard 允許_env_id =複製式劃線板 appwrite_endpoint = https://cloud.appwrite.io/v1 appwrite_project_id = your-project-id appwrite_database_id =您的數據庫-ID appwrite_profile_collection_id = your-id appwrite_assignments_collection_id = your-id appwrite_students_collection_id = your-id jwt_secret =生成 - by-by-running // openssl rand -base64 16 端口= 8080
4。創建文件結構和文件
現在,在文件的根部創建一個SRC文件夾。然後在根文件夾中生成tsconfig.json文件,然後將以下代碼粘貼到其中:
<span>{ </span><span>“ compileroptions”:{ </span><span>“目標”:“ ES6”, </span><span>“模塊”:“ commonjs”, </span><span>“ Outdir”:“ ./ dist”, </span><span>“ eSmoduleInterop”:true, </span><span>“ forceconsistentcasinginfilenames”:是的, </span><span>“嚴格”:是的, </span><span>“ Skiplibcheck”:是的, </span><span>“ resolvejsonmodule”:是的, </span><span>“ baseurl”:“ ./”, </span><span>“路徑”:{ </span><span>“@/*”:[“ src/*”] </span><span>} </span><span>},, </span><span>“包括”:[“ SRC/**/*”], </span><span>“排除”:[“ node_modules”,“ dist”] </span><span>}</span>
此tsconfig.json將打字稿編譯器配置為Target ES6,使用commonjs模塊,然後輸出文件為./dist。它強制執行嚴格的類型檢查,啟用JSON模塊分辨率,為SRC設置路徑別名,並排除Node_Modules和Dist dist。
在SRC文件夾內部,創建以下文件夾:API,配置,控制器,中間件,模型和UTILS。
- UTILS文件夾
- 現在,在UTILS文件夾項目中創建一個新的許可證文件,以使用以下代碼初始化許可證:
<span>從“許可證”導入{許可證}; </span><span>從'../config/environment'導入{puls_api_key}; </span><span>//此行初始化SDK並連接您的node.js應用 </span><span>//到上一步中設置的許可證PDP容器。 </span><span>const許可證=新許可證({ </span><span>//您的API鍵 </span> token <span>:plix_api_key,//將您的API鍵存儲在.env中 </span><span>//在生產中,您可能需要更改此URL以適合您的部署 </span> PDP <span>:'https://cloudpdp.api.permit.io',//默認許可證 </span><span>//如果您希望SDK發射日誌,請輸入以下內容: </span> 紀錄<span>: { </span> 等級<span>:“調試”, </span><span>},, </span><span>//如果您獲得超時 /網絡錯誤,SDK會返回false </span><span>//如果您想提出錯誤,然後讓您處理此錯誤,請輸入以下內容: </span><span>// throneRror:是的, </span><span>}); </span> <span>出口默認許可證;</span>
該文件初始化node.js的許可證SDK,使用存儲在環境中的API鍵將其連接到許可證PDP容器。它配置了調試日誌記錄並設置SDK以默默處理錯誤,除非明確配置將其投擲。
- 接下來,創建一個稱為errorHandler.ts的文件,然後粘貼以下代碼:
<span>//實用程序功能(例如,錯誤處理) </span><span>從'express'導入{請求,響應,nextfunction}; </span> <span>導出const errirhandler =(err:any,req:request,res:ress:reverse,next:next:next function)=> { </span><span>console.error('錯誤:',err.message || err); </span> res <span>.status(err.Status || 500).json({ </span> 錯誤<span>:err.message || “內部服務器錯誤”, </span><span>}); </span><span>};</span>
該文件定義了一個明確的錯誤處理中間件,該中間件記錄錯誤並發送帶有錯誤消息和狀態代碼的JSON響應。如果未提供特定狀態,則默認為500狀態代碼。
- 模型文件夾
- 創建一個名為profile.ts的文件並粘貼以下代碼:
<span>導出接口配置文件{ </span> 名稱<span>:字符串; </span> 電子郵件<span>:字符串; </span> 角色<span>:'admin'| “老師” | '學生'; </span> 用戶ID <span>:字符串; </span><span>}</span>
該文件定義了具有屬性,電子郵件,角色和用戶的屬性的打字稿配置文件接口,其中角色僅限於特定值:管理員,老師或學生。
- 創建sizhtment.ts文件並粘貼以下代碼:
<span>導入{數據庫,id}來自'../config/appwrite'; </span><span>import {database_id,sistionments_collection_id}來自'../config/environment'; </span> <span>導出界面sizgmentData { </span> 標題<span>:字符串; </span> 主題<span>:字符串; </span> className <span>:string; </span> 老師<span>:弦; </span> duedate <span>:string; </span> 創建者郵件<span>:字符串; </span><span>} </span> <span>//創建一個新作業 </span><span>導出異步函數createSignmentIndB(數據:sigsmentmentData){ </span><span>返回等待數據庫。 </span><span>database_id, </span><span>sizgments_collection_id, </span><span>id.unique(), </span> 數據 <span>); </span><span>} </span> <span>//獲取所有作業 </span><span>導出異步函數fetchAssignmentsFromDB(){ </span><span>const響應=等待database.listDocuments(database_id,sissigments_collection_id); </span><span>返迴響應。 </span><span>}</span>
該文件提供了與AppWrite數據庫進行交互的功能,以管理作業。它定義了一個sizhtmentData接口,並包含創建新分配並從數據庫中獲取所有分配的功能。
- 創建一個學生。 TS文件並粘貼以下代碼:
<span>導入{數據庫,ID,權限,角色,查詢}來自'../config/appwrite'; </span><span>導入{database_id,students_collection_id}來自'../config/environment'; </span> <span>導出界面StudentData { </span> firstName <span>:string; </span> lastName <span>:string; </span> 性別<span>:“女孩” | '男孩'| '男孩'| '女孩'; </span> className <span>:string; </span> 年齡<span>:數字; </span> 創建者郵件<span>:字符串; </span><span>} </span> <span>//創建一個新學生 </span><span>導出異步函數CreateStudentIndB(數據:studentData){ </span><span>返回等待數據庫。 </span><span>database_id, </span><span>學生_collection_id, </span><span>id.unique(), </span> 數據<span>, </span><span>[ </span> 許可<span>。閱讀(cole.any()),//公共閱讀許可 </span><span>這是給出的 </span><span>); </span><span>} </span> <span>//獲取所有學生 </span><span>導出異步函數fetchStudentsFromDB(){ </span><span>const響應=等待database.listDocuments(database_id,students_collection_id); </span><span>返迴響應。 </span><span>}</span>
該文件提供了在AppWrite數據庫中管理學生數據的功能。它定義了一個StudentData界面,並包含功能,以創建具有公共閱讀權限的新學生,並從數據庫中獲取所有學生。
- 中間件文件夾
- 創建auth.ts文件並粘貼以下代碼:
<span>從'express'導入{請求,響應,nextfunction}; </span><span>從“ jsonwebtoken”導入JWT; </span> <span>//將請求類型擴展到包含“用戶” </span><span>接口AuthentIcatedRequest擴展了請求{ </span> 用戶<span>? :{ </span> id <span>:string; </span> 角色<span>:字符串; </span><span>}; </span><span>} </span> <span>const authmiddleware =(req:authenticatedRequest,res:reverse,next:next function):void => { </span><span>const token = req.headers.authorization? .split('')[1]; </span> <span>如果(!token){ </span> res <span>.status(401).json({error:'未經授權。no doken提供'}); </span><span>返回 </span><span>} </span> <span>嘗試 { </span><span>const解碼= jwt.verify(token,process.env.jwt_secret!)as {id:string;角色:字符串}; </span> req <span>.user =解碼; </span><span>下一個(); </span><span>} catch(錯誤){ </span> res <span>.status(403).json({error:'無效令牌'}); </span><span>返回 </span><span>} </span><span>}; </span> <span>導出默認authmiddleware;</span>
該文件定義了用於基於JWT的身份驗證的快速中間件。它檢查了請求標頭中的有效令牌,使用秘密鍵驗證它,並將解碼的用戶信息(ID和角色)附加到請求對象。如果令牌丟失或無效,則返回適當的錯誤響應。
- 創建許可證ts並粘貼以下代碼:
<span>從“ ../ Utils/permit”進口許可證; </span> <span>導出const charchusertopermitStudents = async(電子郵件:字符串,操作:字符串,資源:string):Promise <boolean> => { </boolean></span><span>嘗試 { </span><span>const允許=等待許可證。檢查(電子郵件,操作,資源); </span><span>console.log(“允許”,允許); </span><span>退貨允許; </span><span>} catch(錯誤){ </span><span>console.error( <span>`錯誤同步用戶<span>$ {email}</span> to bervile.io:`</span> ,錯誤); </span><span>返回false; </span><span>} </span><span>}; </span> <span>導出const charchusertopermitAssignment = async(電子郵件:字符串,操作:字符串,資源:string):Promise <boolean> => { </boolean></span><span>嘗試 { </span><span>const允許=等待許可證。檢查(電子郵件,操作,資源); </span><span>console.log(“允許”,允許); </span><span>退貨允許; </span><span>} catch(錯誤){ </span><span>console.error( <span>`錯誤同步用戶<span>$ {email}</span> to bervile.io:`</span> ,錯誤); </span><span>返回false; </span><span>} </span><span>};</span>
該文件定義了實用程序功能,checkusertopermitstudents和checkusertoperpertersignment,以在許可證中檢查用戶權限是否有特定的操作和資源。這兩個功能都優雅地處理錯誤,記錄問題並在權限檢查失敗時返回false。它們用於在應用程序中執行授權。
- 控制器文件夾
- 創建auth.ts文件並粘貼以下代碼:
<span>導入{account,id}來自'../config/appwrite'; </span><span>從'express'導入{請求,響應}; </span><span>從“ jsonwebtoken”導入JWT; </span> <span>const jwt_secret = process.env.jwt_secret as String; //確保將其設置在.env文件中 </span> <span>//註冊控制器 </span><span>導出const Ingip = async(req:request,res:response)=> { </span><span>const {電子郵件,密碼,名稱} = req.body; </span> <span>如果(!電子郵件||!密碼||!name){ </span><span>返回res.status(400).json({錯誤:'名稱,電子郵件和密碼是必需的。'}); </span><span>} </span> <span>嘗試 { </span><span>const user =等待帳戶。 </span><span>//生成JWT </span><span>const token = jwt.sign({email},jwt_secret,{expiresin:'8h'}); </span> res <span>.cookie('token',令牌,{ </span> httponly <span>:是的, </span> Samesite <span>:“嚴格”, </span> 安全<span>:是的, </span><span>}); </span> res <span>.status(201).json({成功:true,用戶,token}); </span><span>} catch(錯誤:任何){ </span><span>Console.Error('註冊錯誤:',錯誤); </span> res <span>.status(500).json({成功:false,消息:error.message}); </span><span>} </span><span>}; </span> <span>//登錄控制器 </span><span>導出const login = async(req:request,res:response)=> { </span><span>const {email,passwass} = req.body; </span> <span>如果(!電子郵件||!密碼){ </span><span>返回res.status(400).json({錯誤:需要電子郵件和密碼。'}); </span><span>} </span> <span>嘗試 { </span><span>const session =等待帳戶。 CreateeMailPasswordsession(電子郵件,密碼); </span> <span>//生成無角色的JWT </span><span>const token = jwt.sign( </span><span>{userId:session.userid,email},//不包括任何角色 </span><span>jwt_secret, </span><span>{expiresin:'8H'} </span><span>); </span> res <span>.cookie('token',令牌,{ </span> httponly <span>:是的, </span> Samesite <span>:“嚴格”, </span> 安全<span>:是的, </span><span>}); </span> res <span>.status(200).json({成功:true,token,session}); </span><span>} catch(錯誤:任何){ </span><span>Console.Error('登錄錯誤:',錯誤); </span> res <span>.status(401).json({成功:false,消息:error.message}); </span><span>} </span><span>}; </span> <span>//註銷控制器 </span><span>導出const logout = async(req:request,res:response)=> { </span><span>嘗試 { </span><span>等待帳戶。 deletesessession('當前會話ID'); </span> res <span>.clearcookie('token'); </span> res <span>.status(200).json({成功:true,消息:'成功登錄'}); </span><span>} catch(錯誤:任何){ </span><span>Console.Error('登錄錯誤:',錯誤); </span> res <span>.status(500).json({成功:false,消息:error.message}); </span><span>} </span><span>};</span>
該文件定義了用於註冊,登錄和註銷的身份驗證控制器,與AppWrite集成了用於用戶管理的AppWrite和用於會話處理的JWT。註冊和登錄控制器驗證輸入,創建用戶會話並生成JWT,而註銷控制器清除了會話和令牌。所有控制器都處理錯誤並返回適當的響應。
- 創建sizhtment.ts文件並粘貼以下代碼:
<span>從'express'導入{請求,響應}; </span><span>導入{createSignmentIndb,sissigmentData,fetchAssignmentsfromdb} from'../models/assignment'; </span><span>從'../middleware/permit'import {checkusertopermitAssignment}; </span> <span>//創建一個新作業 </span><span>導出異步函數createSignment(req:request ,res:reverse:wendesp):Promise <void> { </void></span><span>嘗試 { </span><span>const {標題,主題,老師,className,duedate,createMail}:sigsionmentData = req.body; </span> <span>const Ispermittit =等待checkusertopermitAssignment(createRemail,“ create”,“ sistments”); </span><span>如果(! </span> res <span>.status(403).json({error:'未授權'}); </span><span>返回; </span><span>} </span> <span>const newAssignment =等待createSignmentIndb({{ </span> 標題<span>, </span> 主題<span>, </span> 老師<span>, </span> className <span>, </span> 到期日<span>, </span> 創造力 <span>}); </span> <span>console.log(創建新的分配:',new Assignment); </span> res <span>.Status(201).json(newAssignment); </span><span>} catch(錯誤){ </span><span>Console.Error('錯誤創建分配:',錯誤); </span> res <span>.status(500).json({error :( erry as任何時候).message}); </span><span>} </span><span>} </span> <span>//獲取所有作業 </span><span>導出異步函數fetchAssignments(req:request,res:reverse):Promise <void> { </void></span><span>嘗試 { </span><span>const {email} = req.params; </span> <span>const iSpernittit =等待checkusertopermitAssignment(電子郵件,讀取”,“ sigsments”); </span><span>如果(! </span> res <span>.status(403).json({消息:'未授權'}); </span><span>返回; </span><span>} </span> <span>const sistments =等待fetchassignmentsfromdb(); </span> res <span>.Status(200).json(作業); </span><span>} catch(錯誤){ </span> res <span>.status(500).json({error :( erry as任何時候).message}); </span><span>} </span><span>}</span>
該文件定義了用於創建和獲取分配的控制器,以與數據庫集成並允許進行授權檢查。 CreateSignment Controller驗證輸入,檢查權限並創建一個新的分配,而FetchAssignments Controller在驗證訪問後會檢索所有任務。兩個控制器都處理錯誤並返回適當的響應。
- 創建一個學生。 TS文件並粘貼以下代碼:
<span>進口 { </span> CreateStudentIndb <span>, </span> <span>從 </span> StudentData <span>}來自'../ models/student'; </span><span>從'express'導入{請求,響應}; </span><span>從'../middleware/permit'導入{checkusertopermitstudents}; </span> <span>導出異步函數createStudent(req:request,res:reverse):Promise <void> { </void></span><span>嘗試 { </span><span>const {firstName,lastName,性別,className,age,createMail}:studentData = req.body; </span> <span>如果(!['girl','boy']。包括(性別)){ </span> res <span>.status(400).json({error:'無效性別類型'}); </span><span>返回; </span><span>} </span> <span>const iSpernits =等待checkusertopermitstudents(creatsoremail,“創建”,“學生”); </span><span>如果(! </span> res <span>.status(403).json({消息:'未授權'}); </span><span>返回; </span><span>} </span> <span>const newstudent =等待createStudentIndb({ </span> 名<span>, </span> 姓<span>, </span> 性別<span>, </span> className <span>, </span> 年齡<span>, </span> 創造力 <span>}); </span> res <span>.Status(201).JSON(NEWSTUDENT); </span><span>} catch(錯誤){ </span> res <span>.status(500).json({error :( erry as任何時候).message}); </span><span>} </span><span>} </span> <span>//獲取所有學生 </span><span>導出異步函數提取器(req:request,res:reverse):Promise <void> { </void></span><span>嘗試 { </span><span>const {email} = req.params; </span> <span>const iSpernits =等待checkusertopermitstudents(電子郵件,“讀”,“學生”); </span><span>如果(! </span> res <span>.status(403).json({消息:'未授權'}); </span><span>返回; </span><span>} </span> <span>const學生=等待fetchstudents fromdb(); </span> Res <span>.Status(200).Json(學生); </span><span>} catch(錯誤){ </span> res <span>.status(500).json({error :( erry as任何時候).message}); </span><span>} </span><span>}</span>
該文件定義用於創建和獲取學生的控制器,與數據庫集成並允許進行授權檢查。 CreateStudent Controller驗證輸入,檢查權限並創建新學生,而獲取研究員控制器在驗證訪問後會檢索所有學生。兩個控制器都處理錯誤並返回適當的響應。
- 創建一個profile.ts文件並粘貼以下代碼:
<span>導入{profile}來自'@/models/profile'; </span><span>從“ Axios”導入Axios; </span><span>從'../config/appwrite'import {數據庫,id,query}; </span><span>導入{請求,響應,next功能,requestHandler}來自'express'; </span><span>從'../config/environment'導入{puls_api_key}; </span> <span>const profileId = process.env.appwrite_profile_collection_id as String; //確保這是.env </span><span>const databaseId = process.env.appwrite_database_id as String; //確保這是.env </span><span>const projectId = process.env.permit_project_id as String </span><span>const Environment = process.env.permit_env_id作為字符串 </span> <span>const plase_api_url = <span>`https://api.permit.io/v2/facts/ <span>$ {projectID}</span> / <span>$ {emoventionId}</span> /users`</span> ; </span><span>const plass_auth_header = { </span> 授權<span>: <span>`bearer <span>$ {plum_api_key}</span> `</span> , </span><span>“ content-type”:“ application/json”, </span><span>}; </span> <span>//創建個人資料控制器 </span><span>導出const constrecrofile:requestHandler = async(req:request,res:respeart of stempter:reverse,next:next function):Promise <void> => { </void></span><span>const {firstName,lastName,email,cool,userId} = req.body; </span><span>console.log(req.body); </span> <span>if(!email ||!角色||!userId){ </span> res <span>.status(400).json({錯誤:'firstName,lastname,email,cool和userId是必需的。'}); </span><span>返回; </span><span>} </span> <span>//驗證角色 </span><span>const允許的:profile ['remo'] [] = ['admin','老師','student']; </span><span>if(!washeRoles.cimludes(cole)){ </span> res <span>.status(400).json({錯誤:'無效角色。 </span><span>返回; </span><span>} </span> <span>嘗試 { </span><span>const newuser =等待數據庫。 </span> 資料庫<span>, </span> profileid <span>, </span><span>id.unique(), </span><span>{firstName,lastName,電子郵件,角色,用戶iD} </span><span>); </span><span>//步驟2:同步用戶允許oio </span><span>const允許載荷= { </span> 鑰匙<span>:電子郵件, </span> 電子郵件<span>, </span> first_name <span>:firstName, </span> last_name <span>:lastname, </span> 角色_ASSIGNMENTS <span>:[{{角色,租戶:“默認”}], </span><span>}; </span> <span>讓允許回答; </span><span>嘗試 { </span><span>const響應=等待axios.post(允許_api_url,periverpayload,{標題:pers_auth_header}); </span> periverResponse <span>= wendesp.data; </span><span>console.log(“用戶同步到允許。 </span><span>} catch(periverError){ </span><span>if(axios.isaxioserror(periverError)){ </span><span>Console.Error(“無法同步用戶允許。 </span><span>} 別的 { </span><span>Console.Error(“未能同步用戶允許。 </span><span>} </span> puroperresponse <span>= {error:“無法與許可證同步”}; </span><span>} </span> <span>//步驟3:返回兩個回复 </span> res <span>.status(201).json({ </span> 消息<span>:“成功創建的用戶配置文件”, </span> 用戶<span>:Newuser, </span> 許可證<span>:許可證, </span><span>}); </span><span>返回; </span><span>} catch(錯誤:任何){ </span> res <span>.status(500).json({成功:false,消息:error.message}); </span><span>返回; </span><span>} </span><span>}; </span> <span>//通過電子郵件獲取個人資料 </span><span>導出const const getProfileByeMail = async(req:request,res:ress:reverse,next:next function):Promise <void> => { </void></span><span>const {email} = req.params; </span> <span>如果(!email){ </span> res <span>.status(400).json({錯誤:'電子郵件是必需的。'}); </span><span>返回; </span><span>} </span> <span>嘗試 { </span><span>const profile =等待數據庫。 listDocuments( </span> 資料庫<span>, </span> profileid <span>, </span><span>[query.equal(“電子郵件”,電子郵件)] </span><span>); </span> <span>if(profile.documents.length === 0){ </span> res <span>.status(404).json({error:'profile找不到'}); </span><span>返回; </span><span>} </span> res <span>.status(200).json({成功:true,profile:profile.documents [0]}); </span><span>} catch(錯誤:任何){ </span><span>Console.Error('錯誤獲取配置文件:',錯誤); </span> res <span>.status(500).json({成功:false,消息:error.message}); </span><span>} </span><span>};</span>
該文件定義了用於創建和獲取用戶配置文件的控制器,與AppWrite集成以進行數據庫操作,並允許角色同步。 CreateProfile控制器驗證輸入,創建配置文件並同步用戶允許,而GetProfileByemail Controller通過電子郵件檢索配置文件。兩個控制器都處理錯誤並返回適當的響應。
- 配置文件夾
- 創建AppWrite.ts文件並粘貼以下代碼:
<span>導入{客戶端,帳戶,數據庫,存儲,ID,權限,角色,查詢}來自'appWrite'; </span><span>導入{appwrite_endpoint,appwrite_project_id,appwrite_api_key}來自'./environment'; </span> <span>//初始化appwrite客戶端 </span><span>const客戶端=新客戶端() </span><span>.setEndpoint(AppWrite_Endpoint)// AppWrite Endpoint </span><span>.setProject(appwrite_project_id); // AppWrite項目ID </span> <span>//添加API密鑰(如果可用)(用於服務器端操作) </span><span>if(appwrite_api_key){ </span><span>(client as not).config.key = appwrite_api_key; //解決方案以設置API密鑰 </span><span>} </span> <span>//初始化appwrite服務 </span><span>const帳戶=新帳戶(客戶端); </span><span>const數據庫=新數據庫(客戶端); </span><span>const Storage =新存儲(客戶端); </span> <span>//導出AppWrite客戶端和服務 </span><span>導出{客戶端,帳戶,數據庫,存儲,ID,權限,角色,查詢};</span>
此文件初始化並使用項目端點,ID和可選的API密鑰配置AppWrite客戶端。它還設置並導出AppWrite服務(例如帳戶,數據庫和存儲),以及ID,權限,角色和查詢等實用程序常數。
- 創建環境.ts文件並粘貼以下代碼:
<span>從“ dotenv”導入dotenv; </span>dotenv <span>.config(); //來自.env的加載環境變量 </span> <span>導出const appwrite_endpoint = process.env.appwrite_endpoint || ''; </span><span>導出const plus_api_key = process.env.permit_api_key || ''; </span><span>導出const const_project_id = process.env.permit_project_id || ''; </span><span>導出const plass_env_id = process.env.permit_env_id || ''; </span><span>導出const appwrite_project_id = process.env.appwrite_project_id || ''; </span><span>導出const database_id = process.env.appwrite_database_id || ''; </span><span>導出const susident_collection_id = process.env.appwrite_students_collection_id || ''; </span><span>導出const signtments_collection_id = process.env.appwrite_assignments_collection_id || ''; </span> <span>導出const profile_collection_id = process.env.appwrite_profile_collection_id || '';</span>
該文件從.env文件加載環境變量,並將其作為用於應用程序中的常數,例如AppWrite和允許配置,數據庫ID和Collection ID。如果未設置環境變量,則將默認值提供為後備。
- API文件夾
- 創建student.ts並粘貼以下代碼:
<span>從“ Express”導入Express; </span><span>從'../controllers/student'導入{createStudent,fetchstudents}; </span><span>從“ ../ middleware/auth”導入authmiddleware; </span> <span>const router = express.router(); </span> <span>//定義與學生相關的終點 </span>路由器<span>.post('/students',authmiddleware,createStudent); //創建一個新學生 </span>Router <span>.get('/student/:email',authmiddleware,fetchStudents); //獲取所有學生 </span><span>導出默認路由器; //導出路由器實例</span>
該文件設置了一個帶有用於管理學生數據的端點的明確路由器。它包括用於創建新學生和獲取學生的路線,均受身份驗證中間件(authmiddleware)保護。然後將路由器導出以用於應用程序。
- 創建auth.ts文件並粘貼以下代碼:
<span>// src/routes/authRoutes.ts </span><span>從“ Express”導入Express; </span><span>從'../controllers/auth'; </span> <span>const router = express.router(); </span> <span>//定義與驗證相關的終點 </span>路由器<span>.post('//impost',(req,res,next)=> {//註冊路由 </span><span>Indip(req,res)。然後(()=> { </span><span>下一個(); </span><span>})。 catch((err)=> { </span><span>下一個(err); </span><span>}); </span><span>}); </span>路由器<span>.post('/login',(req,res,sext)=> {//登錄路由 </span><span>登錄(req,res)。然後(()=> { </span><span>下一個(); </span><span>})。 catch((err)=> { </span><span>下一個(err); </span><span>}); </span><span>}); </span>路由器<span>.post('/logout',logout); //註銷路線 </span><span>導出默認路由器; //導出路由器實例</span>
該文件設置了一個帶有端點的快速路由器,用於與身份驗證相關的操作,包括用戶註冊,登錄和註銷。註冊和登錄路由可以使用錯誤處理的異步操作,而註銷路由很簡單。路由器已導出以用於應用程序。
- 創建sizhtment.ts文件並粘貼以下代碼:
<span>從“ Express”導入Express </span><span>從“ ../controllers/assignment”導入{createSignment,fetchAssignments} </span><span>從“ ../middleware/auth”導入authmiddleware </span> <span>const router = express.router() </span> 路由器<span>.post(“/create”,authmiddleware,createSignment) </span>路由器<span>.get(“/:電子郵件”,authmiddleware,fetchAssignments) </span><span>導出默認路由器</span>
該文件設置了一個帶有用於管理作業的端點的快線路由器。它包括用於創建分配和獲取作業的路由,均受身份驗證中間件(authmiddleware)保護。路由器已導出以用於應用程序。
- 創建profile.ts文件並粘貼以下代碼:
<span>從“ Express”導入Express; </span><span>從'../controllers/profile'import {createProfile,getProfileByemail}; </span><span>從“ ../ middleware/auth”導入authmiddleware; </span> <span>const router = express.router(); </span> <span>//創建個人資料的路線 </span>路由器<span>.post('/profile',authmiddleware,createProfile); </span> <span>//通過電子郵件獲取個人資料的路線 </span>router <span>.get('/profile/:email', authMiddleware, getProfileByEmail); </span><span>export default router;</span>
This file sets up an Express router with endpoints for managing user profiles. It includes routes for creating a profile and fetching a profile by email, both protected by an authentication middleware (authMiddleware). The router is exported for use in the application.
- Create index.ts file and paste the following code:
<span>import express, { Request, Response } from 'express'; </span><span>import dotenv from 'dotenv'; </span><span>import cors from 'cors'; // CORS middleware </span><span>import authRoutes from './auth'; // Import auth routes </span><span>import profileRoutes from './profile'; </span><span>import studentRoutes from './student'; </span><span>import assignmentRoutes from './assignment'; </span><span>import { errorHandler } from '../utils/errorHandler'; // Custom error handler middleware </span> dotenv <span>.config(); // Load environment variables from .env file </span> <span>const app = express(); </span><span>const PORT = process.env.PORT || 8080; </span> <span>// 中介軟體 </span>app <span>.use(cors()); // Handle CORS </span>app <span>.use(express.json()); /// Parse incoming JSON requests </span> <span>// Routes </span>app <span>.use('/api/auth', authRoutes); // Authentication routes </span>app <span>.use('/api', profileRoutes); // Profile routes mounted </span>app <span>.use('/api', studentRoutes); // Student routes mounted </span>app <span>.use('/api/assignments', assignmentRoutes); // Assignment routes mounted </span> <span>// Global Error Handling Middleware </span>app <span>.use(errorHandler); // Handle errors globally </span> <span>// Default Route </span>app <span>.get('/', (req: Request, res: Response) => { </span> res <span>.send('Appwrite Express API'); </span><span>}); </span> <span>// Start Server </span>app <span>.listen(PORT, () => { </span><span>console.log( <span>`Server is running on port <span>${PORT}</span> `</span> ); </span><span>}); </span><span>export default app;</span>
This file sets up an Express server, configuring middleware like CORS and JSON parsing, and mounts routes for authentication, profiles, students, and assignments. It includes a global error handler and a default route to confirm the server is running. The server listens on a specified port, logs its status, and exports the app instance for further use.
- Finally, to run this project, change a part of package.json and install the following packages below so when you run npm run dev, it works.
- Install packages:
npm install concurrently ts-node nodemon --save-dev
- By updating the scripts in the package.json, when you start the server, the typescript files are compiled to JavaScript in a new folder that is automatically created called dist
"scripts": { "dev": "concurrently \"tsc --watch\" \"nodemon -q --watch src --ext ts --exec ts-node src/api/index.ts\"", "build": "tsc", "start": "node ./dist/api/index.js" },,
Now run npm run dev to start your server. When you see this message, it means that you have successfully implemented the backend.

Congratulations, your backend is ready for requests.
Now that our backend is set up, move on to frontend integration, where you'll:
- Secure API requests from Next.js
- Dynamically show/hide UI elements based on user permissions.
Reason for creating an extensive backend service using Appwrite
Appwrite is often described as a backend-as-a-service (BaaS) solution, meaning it provides ready-made backend functionality like authentication, database management, and storage without requiring developers to build a traditional backend.
However, for this project, I needed more flexibility and control over how data was processed, secured, and structured, which led me to create an extensive custom backend using Node.js and Express while still leveraging Appwrite's services.
Instead of relying solely on Appwrite's built-in API calls from the frontend, I designed a Node.js backend that acted as an intermediary between the frontend and Appwrite. This allowed me to:
- Implement fine-grained access control with Permit.io before forwarding requests to Appwrite.
- Structure API endpoints for multi-tenancy to ensure tenant-specific data isolation.
- Create custom business logic, such as processing role-based actions before committing them to the Appwrite database.
- Maintain a centralized API layer, making it easier to enforce security policies, log activities, and scale the application.
Appwrite provided the core authentication and database functionality of this application, but this additional backend layer enhanced security, flexibility, and maintainability, to ensure strict access control before any action reached Appwrite.
結論
That's it for part one of this article series. In part 2, we'll handle the frontend integration by setting up API calls with authorization, initializing and installing necessary dependencies, writing out the component file codes, and handling state management & routes.
以上是使用Next.js(後端集成)構建多租戶SaaS應用程序的詳細內容。更多資訊請關注PHP中文網其他相關文章!

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

JavaScript在現實世界中的應用包括服務器端編程、移動應用開發和物聯網控制:1.通過Node.js實現服務器端編程,適用於高並發請求處理。 2.通過ReactNative進行移動應用開發,支持跨平台部署。 3.通過Johnny-Five庫用於物聯網設備控制,適用於硬件交互。

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。

JavaScript的最新趨勢包括TypeScript的崛起、現代框架和庫的流行以及WebAssembly的應用。未來前景涵蓋更強大的類型系統、服務器端JavaScript的發展、人工智能和機器學習的擴展以及物聯網和邊緣計算的潛力。

JavaScript是現代Web開發的基石,它的主要功能包括事件驅動編程、動態內容生成和異步編程。 1)事件驅動編程允許網頁根據用戶操作動態變化。 2)動態內容生成使得頁面內容可以根據條件調整。 3)異步編程確保用戶界面不被阻塞。 JavaScript廣泛應用於網頁交互、單頁面應用和服務器端開發,極大地提升了用戶體驗和跨平台開發的靈活性。

Python更适合数据科学和机器学习,JavaScript更适合前端和全栈开发。1.Python以简洁语法和丰富库生态著称,适用于数据分析和Web开发。2.JavaScript是前端开发核心,Node.js支持服务器端编程,适用于全栈开发。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

Safe Exam Browser
Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

MantisBT
Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

SAP NetWeaver Server Adapter for Eclipse
將Eclipse與SAP NetWeaver應用伺服器整合。

SublimeText3 英文版
推薦:為Win版本,支援程式碼提示!

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)