搜索
首页web前端js教程使用Next.js(后端集成)构建多租户SaaS应用程序

使用Next.js(后端集成)构建多租户SaaS应用程序

我使用您的日常技术工具构建了功能性的多租户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用于身份验证和数据库管理,并允许授权精细。

技术堆栈概述

Technologypurposenext.jsfrontend Frameworkshadcn tailwindcssui组件和StylingzustandState ManagementAppWriteAuthentication&Backendpermit.Eorole基于访问控制

系统体系结构

该应用程序遵循后端优先的方法:

  1. 后端(node.js express)
    • 处理API请求和业务逻辑。
    • 使用AppWrite进行身份验证和数据库管理。
    • 工具允许授权,定义角色和权限。
    • 确保在数据访问之前验证每个请求。
  2. 前端(Next.js)
    • 连接到后端以安全地获取数据。
    • 使用基于角色的UI渲染,这意味着用户只能看到他们授权访问的内容。
    • 根据权限限制操作(例如创建作业)。

通过在API级别执行授权,我们确保用户无法绕过限制,即使他们操纵前端。

在本指南的末尾,您将拥有一个功能齐全的多租户Edtech SaaS应用程序,其中:

  • 管理员可以添加和查看学生。
  • 教师可以添加和查看学生,并创建作业。
  • 学生只能查看他们指定的课程。

本文提供了我如何实施许可来处理构建该项目的授权的分步分类,因此请跟随并构建您的项目。

获得许可证的后端实施

要执行基于角色的访问控制(RBAC)和租户隔离,我们需要:

  1. 设置许可证并定义角色,租户和政策。
  2. 在后端(Node.js Express)集成许可证。
  3. 使用中间件在允许请求之前检查权限。

让我们逐步吧。

1。设置许可证

在编写任何代码之前,您需要

  • 根据许可创建帐户。
使用Next.js(后端集成)构建多租户SaaS应用程序

您将出现入职,但是一旦输入组织名称,就可以跳过设置。

  • 创建资源和动作

导航到策略部分,您将创建一个可以在该资源上执行的资源和操作。

使用Next.js(后端集成)构建多租户SaaS应用程序

创建资源后,应该看起来像这样:

使用Next.js(后端集成)构建多租户SaaS应用程序
  • 创造角色

创建资源后,使用“角色”选项卡导航到角色页面。您会发现某些角色已自动分配。

使用Next.js(后端集成)构建多租户SaaS应用程序

删除这些角色并创建新角色。每个角色都将具有与用户可以做什么和不能做什么相关的特定规则。首先创建管理员角色,因为它将作为RBAC条件的构建块。单击顶部的添加角色按钮并创建角色。

使用Next.js(后端集成)构建多租户SaaS应用程序

创建角色后,应该看起来像这样:

使用Next.js(后端集成)构建多租户SaaS应用程序

伟大的!

现在您已经创建了资源和角色,现在可以在策略编辑器中配置权限。

  • 在策略编辑器中配置权限

返回策略编辑器,这就是角色现在的样子,每个资源定义了每个资源以及您可以选择的操作。现在,您可以在资源上执行所选操作的角色权限。

使用Next.js(后端集成)构建多租户SaaS应用程序

完成每个角色的操作后,请单击页面右下方的“保存更改”按钮。

  • 复制API键

最后,要使用许可证的云PDP,您将需要当前环境的API键。对于此项目,您将使用开发环境密钥。继续进行设置,然后单击API键,向下滚动到环境API键,单击“显示键”,然后复制它。

使用Next.js(后端集成)构建多租户SaaS应用程序

设置许可仪表板后,您现在可以继续前进。

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文件中。
使用Next.js(后端集成)构建多租户SaaS应用程序

您的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数据库中创建以下属性:

  • 配置文件集合
使用Next.js(后端集成)构建多租户SaaS应用程序
  • 学生收集
使用Next.js(后端集成)构建多租户SaaS应用程序
  • 分配收集
使用Next.js(后端集成)构建多租户SaaS应用程序

目前,您的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>路由器<span>.get('/profile/:email',authmiddleware,getProfileByeMail);
</span><span>导出默认路由器;</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.

使用Next.js(后端集成)构建多租户SaaS应用程序

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中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
JavaScript的核心:它是在C还是C上构建的?JavaScript的核心:它是在C还是C上构建的?May 05, 2025 am 12:07 AM

javascriptisnotbuiltoncorc; saninterpretedlanguagethatrunsonenginesoftenwritteninc.1)javascriptwasdesignedAsalightweight,解释edganguageforwebbrowsers.2)Enginesevolvedfromsimpleterterterpretpreterterterpretertestojitcompilerers,典型地提示。

JavaScript应用程序:从前端到后端JavaScript应用程序:从前端到后端May 04, 2025 am 12:12 AM

JavaScript可用于前端和后端开发。前端通过DOM操作增强用户体验,后端通过Node.js处理服务器任务。1.前端示例:改变网页文本内容。2.后端示例:创建Node.js服务器。

Python vs. JavaScript:您应该学到哪种语言?Python vs. JavaScript:您应该学到哪种语言?May 03, 2025 am 12:10 AM

选择Python还是JavaScript应基于职业发展、学习曲线和生态系统:1)职业发展:Python适合数据科学和后端开发,JavaScript适合前端和全栈开发。2)学习曲线:Python语法简洁,适合初学者;JavaScript语法灵活。3)生态系统:Python有丰富的科学计算库,JavaScript有强大的前端框架。

JavaScript框架:为现代网络开发提供动力JavaScript框架:为现代网络开发提供动力May 02, 2025 am 12:04 AM

JavaScript框架的强大之处在于简化开发、提升用户体验和应用性能。选择框架时应考虑:1.项目规模和复杂度,2.团队经验,3.生态系统和社区支持。

JavaScript,C和浏览器之间的关系JavaScript,C和浏览器之间的关系May 01, 2025 am 12:06 AM

引言我知道你可能会觉得奇怪,JavaScript、C 和浏览器之间到底有什么关系?它们之间看似毫无关联,但实际上,它们在现代网络开发中扮演着非常重要的角色。今天我们就来深入探讨一下这三者之间的紧密联系。通过这篇文章,你将了解到JavaScript如何在浏览器中运行,C 在浏览器引擎中的作用,以及它们如何共同推动网页的渲染和交互。JavaScript与浏览器的关系我们都知道,JavaScript是前端开发的核心语言,它直接在浏览器中运行,让网页变得生动有趣。你是否曾经想过,为什么JavaScr

node.js流带打字稿node.js流带打字稿Apr 30, 2025 am 08:22 AM

Node.js擅长于高效I/O,这在很大程度上要归功于流。 流媒体汇总处理数据,避免内存过载 - 大型文件,网络任务和实时应用程序的理想。将流与打字稿的类型安全结合起来创建POWE

Python vs. JavaScript:性能和效率注意事项Python vs. JavaScript:性能和效率注意事项Apr 30, 2025 am 12:08 AM

Python和JavaScript在性能和效率方面的差异主要体现在:1)Python作为解释型语言,运行速度较慢,但开发效率高,适合快速原型开发;2)JavaScript在浏览器中受限于单线程,但在Node.js中可利用多线程和异步I/O提升性能,两者在实际项目中各有优势。

JavaScript的起源:探索其实施语言JavaScript的起源:探索其实施语言Apr 29, 2025 am 12:51 AM

JavaScript起源于1995年,由布兰登·艾克创造,实现语言为C语言。1.C语言为JavaScript提供了高性能和系统级编程能力。2.JavaScript的内存管理和性能优化依赖于C语言。3.C语言的跨平台特性帮助JavaScript在不同操作系统上高效运行。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器

VSCode Windows 64位 下载

VSCode Windows 64位 下载

微软推出的免费、功能强大的一款IDE编辑器

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )专业的PHP集成开发工具

螳螂BT

螳螂BT

Mantis是一个易于部署的基于Web的缺陷跟踪工具,用于帮助产品缺陷跟踪。它需要PHP、MySQL和一个Web服务器。请查看我们的演示和托管服务。

WebStorm Mac版

WebStorm Mac版

好用的JavaScript开发工具