私はあなたの日常的な技術ツールを使用して機能的なマルチテナントSaaSアプリケーション(EDTECHアプリ)を作成しましたが、あなたは同じことをすることができます。
まず、マルチテナントSaaSアプリケーションとは何ですか?
マルチテナントSaaSアプリケーションを使用すると、単一のコードベースから複数の顧客にサービスを提供できます。しかし、これを行うには、安全でテナント固有のアクセスを管理する必要があります。これは、手動で行われた場合に挑戦する可能性があります。そのため、このプロセスを簡素化する最新の認可ツールであるPermitを使用することにしました。
この記事では、next.jsとappwriteでテナントの分離とロールベースのアクセス制御(RBAC)を備えたデモアプリを構築する段階的な例を使用して、許可を使用してSaaSアプリケーションの承認を簡素化する方法を紹介します。
next.jsとappwriteとは何ですか、そしてなぜそれらが必要なのですか?
next.js
Next.jsは、サーバー側のレンダリング(SSR)、静的サイト生成(SSG)、APIルート、およびパフォーマンスの最適化をボックスから提供するReactベースのフレームワークです。
このプロジェクトでは、次のためにnext.jsを使用しました。
- これにより、パフォーマンスとSEOが改善されるページの事前レンダリングが可能になります。
- 組み込みのルーティングにより、ページのトランジションと動的コンテンツを簡単に管理できます。
- 認証と承認のために、AppWriteやPermit.ioなどのバックエンドサービスと簡単に統合できます。
appwrite
AppWriteは、ユーザー認証、データベース、ストレージ、サーバーレス機能を提供するサービスとしてのバックエンド(BAAS)プラットフォームです。 AppWriteのようなサービスを使用すると、バックエンドをゼロから構築する必要性がなくなるため、バックエンド機能にアクセスしながらフロントエンド開発に集中できます。
このプロジェクトでは、appwriteを使用しました。
- ユーザーの登録、ログイン、およびセッション管理を処理する。
- 構造化されたNOSQLデータベースを提供して、テナント固有のデータを保存します。
next.jsとappwriteを一緒に使用すると、開発プロセスを効率的に保ちながら、スケーラブルで高性能のマルチテナントSaaSアプリを作成することができました。
マルチテナントSaaS認証の紹介
マルチテナントSAASアプリは、アプリケーションの単一のソフトウェアインスタンスを使用して、テナントと呼ばれる複数のユーザーまたはユーザーのグループにサービスを提供するソフトウェアです。
それが意味するのは、マルチテナントSaaSアーキテクチャでは、複数の顧客(テナント)が同じアプリケーションインフラストラクチャを共有するか、同じアプリケーションを使用しますが、データの分離を維持することです。
これの実用的な例は、Trelloのようなプロジェクト管理ツールです。
- これは、共有サーバーで実行され、すべてのユーザーに対して同じコードベースを持っている単一のインフラストラクチャです。
- Trelloを使用している各企業(例えば、A Company AおよびCompany B)はテナントです。
- データを分離します:
- 会社Aの従業員は、プロジェクト、タスク、ボードのみを見ることができます。
- B社の従業員は、会社Aのデータにアクセスしたり表示したりすることはできません。その逆も同様です。
これにより、リソースが共有されている間、各テナントのデータとアクティビティがプライベートで安全であることが保証されます。
マルチテナントアプリケーションでは、テナント内であっても、一部のユーザーは一部の情報へのアクセスが高くなりますが、一部のメンバーは特定のリソースに制限されます。
そのようなアプリケーションの承認は次のとおりです。
- ユーザーが他のテナントまたは顧客のデータまたはリソースにアクセスできないようにします。これは、テナントの分離と呼ばれます。
- テナント内のユーザーが、粒状アクセス制御を提供することにより、役割の許可のみにアクセスできるようにします。
- パフォーマンスを遅らせたり低下させたりすることなく、より多くのユーザー、テナント、および役割を処理します。
テナントの分離と粒状アクセス制御の重要性
テナントの分離は、各顧客の情報がプライベートなままであることを確認することにより、データを安全に保ちます。 Granular Access Controlは、組織内のユーザーが必要なアクセス許可のみを取得することを保証します。
SaaSアプリに許可を実装することは複雑で難しい場合がありますが、許可などの承認ツールがある場合はそうではありません。
許可とは何ですか、そしてその利点は何ですか?
許可は、マルチテナントアプリを含むあらゆるアプリケーションでアクセスを管理するための使いやすい許可ツールです。アプリケーションでpermit.ioを使用すると、アプリケーション内のアクセス制御用の特定のアクセス許可を使用して役割を簡単に定義および割り当てることができます。アプリケーション内の役割を作成する以外に、ユーザーまたはリソース属性に基づいて条件とルールを追加して、各ユーザーができることとできないことを指定することもできます。
許可とその利点について知っておくべきことのほとんどを知っているので、主要な取引に入りましょう。SaaSアプリケーションをNext.JSと構築し、許可のために許可を統合します。
許可の力を実証するために、マルチテナントのEdtech SaaSプラットフォームを構築します。
Edtech SaaSプラットフォームの構築には、ユーザー認証、ロールベースのアクセス制御(RBAC)、マルチテナンシーなど、いくつかの課題が含まれます。 FrontendにはNext.js、認証とデータベース管理のためのAppWrite、および微調整された承認を許可します。
技術スタックの概要
システムアーキテクチャ
アプリケーションは、バックエンドファーストアプローチに従います。
- バックエンド(node.js express)
- APIリクエストとビジネスロジックを処理します。
- 認証とデータベース管理にAppWriteを使用します。
- 許可、役割と許可の定義の許可を実装します。
- データアクセス前にすべてのリクエストが検証されるようにします。
- フロントエンド(next.js)
- バックエンドに接続して、データを安全に取得します。
- 役割ベースのUIレンダリングを使用します。つまり、ユーザーがアクセスを許可されているもののみを確認します。
- アクションを制限します(アクションを作成するなど)。
APIレベルで承認を実施することにより、フロントエンドを操作しても、ユーザーが制限をバイパスできないようにします。
このガイドの最後に、完全に機能的なマルチテナントEdtech SaaSアプリがあります。
- 管理者は生徒を追加して表示できます。
- 教師は、生徒を追加および表示したり、割り当てを作成したりできます。
- 学生は、割り当てられたコースワークのみを見ることができます。
この記事では、このプロジェクトを構築するための許可を処理する許可を実装した方法の段階的な内訳を提供します。
許可証によるバックエンドの実装
ロールベースのアクセス制御(RBAC)とテナントの分離を実施するには、次のことが必要です。
- 許可を設定し、役割、テナント、およびポリシーを定義します。
- バックエンド(node.js express)に許可を統合します。
- リクエストを許可する前に、アクセス許可をチェックするミドルウェアを使用してAPIルートを保護します。
ステップバイステップに行きましょう。
1.設定許可
コードを書く前に、する必要があります
- 許可時にアカウントを作成します。

オンボーディングが表示されますが、組織名を入力したら、セットアップをスキップできます。
- リソースとアクションを作成します
ポリシーセクションに移動し、そのリソースで実行できるリソースとアクションを作成します。

リソースの作成が完了したら、次のようになります。

- 役割の作成
リソースを作成した後、[ロール]タブを使用して[役割]ページに移動します。いくつかの役割が自動的に割り当てられていることがわかります。

これらの役割を削除し、新しい役割を作成します。各役割には、ユーザーができることとできないことについて、特定のルールが関連付けられています。後でRBAC条件のビルディングブロックとして機能するため、最初に管理者の役割を作成します。上部の[ロールの追加]ボタンをクリックして、ロールを作成します。

役割の作成が完了したら、次のようになります。

素晴らしい!
リソースと役割を作成したので、ポリシーエディターにアクセス許可を構成できるようになりました。
- ポリシーエディターでのアクセス許可の構成
ポリシーエディターに戻ると、これが今ではロールがどのように見えるかであり、個々のリソースが定義され、選択できるアクションがあります。これで、リソース上で選択したアクションを実行するために役割に権限を与える準備ができました。

各ロールのアクションの選択が完了したら、ページの右下にある[変更変更]ボタンをクリックします。
- APIキーをコピーします
最後に、許可のクラウドPDPを使用するには、現在の環境のAPIキーが必要になります。このプロジェクトでは、開発環境キーを使用する予定です。設定に進み、APIキーをクリックし、環境APIキーまでスクロールし、[キーを表示]をクリックしてコピーします。

許可ダッシュボードを設定した後、バックエンドに移動できます。
2。依存関係のインストール
開始するには、コンピューターにnode.jsをインストールする必要があります。システムにnode.jsがインストールされていることを確認した後、次の手順に従ってください。
- 次のコマンドを使用して新しいプロジェクトを作成することから始めます。
mkdirバックエンド CD Backendnpm init -y
- 次に、次のパッケージをインストールします。
NPMインストールExpress dotenv permitio cors appwwrite axios jsonwebtoken
- Expressで許可を構成します。 .envファイルで、APIキーを保存します。
pimmit_api_key = your-permit-key-you-copied-earrier
3. AppWriteの設定
- AppWriteに移動して、プロジェクト名を入力して地域を選択して新しいプロジェクトを作成します。プロジェクトIDおよびAPIエンドポイントを書き留めます。これが、.envファイルの値として入力するものです。あなたのenvファイルは次のようになるはずです:
pimmit_api_key = your-permit-key-you-copied-earrier appwrite_endpoint = https://cloud.appwrite.io/v1 appwrite_project_id = your-project-id
- 次に、データベースに進んでデータベースを作成し、データベースIDをコピーしてenvファイルに貼り付けます。

あなたのenvファイルは今次のようになるはずです:
pimmit_api_key = your-permit-key-you-copied-earrier appwrite_endpoint = https://cloud.appwrite.io/v1 appwrite_project_id = your-project-id appwrite_database_id = your-database-id
次の属性を使用して、次のコレクションをAppWriteデータベースに作成します。
- プロファイルコレクション

- 学生コレクション

- 割り当てコレクション

この時点であなたのenvファイルがどのように見えるべきか:
pimmit_api_key = your-permit-key-you-copied-earrier pimmit_project_id = copy-from-dashboard pimmit_env_id = copy-from-dashboard appwrite_endpoint = https://cloud.appwrite.io/v1 appwrite_project_id = your-project-id appwrite_database_id = your-database-id appwrite_profile_collection_id = your-id appwrite_assignments_collection_id = your-id appwrite_students_collection_id = your-id jwt_secret = generate-this-by-running // openssl rand -base64 16 ポート= 8080
4.ファイル構造とファイルを作成します
次に、ファイルのルートにSRCフォルダーを作成します。次に、ルートフォルダーでtsconfig.jsonファイルを生成し、次のコードを貼り付けます。
<span>{ </span><span>「コンパイラポーション」:{ </span><span>「ターゲット」:「es6」、 </span><span>「モジュール」:「commonjs」、 </span><span>「oututdir」:「./dist」、 </span><span>「esmoduleinterop」:本当です </span><span>「forcossistentcasinginfilenames」:本当、 </span><span>「厳格」:本当、 </span><span>「Skiplibcheck」:本当、 </span><span>「ResolveJSonmodule」:本当、 </span><span>「baseurl」:「./」 </span><span>「パス」:{ </span><span>"@/*":["src/*"] </span><span>} </span><span>}、 </span><span>"include":["src/**/*"]、 </span><span>「除外」:["node_modules"、 "dist"] </span><span>}</span>
このtsconfig.jsonは、ES6をターゲットにし、CommonJSモジュールを使用し、ファイルを./Distに出力するようにTypeScriptコンパイラを構成します。厳密なタイプチェックを強制し、JSONモジュールの解像度を有効にし、SRCのパスエイリアスを設定し、node_modulesを除外し、コンパイルから除外します。
SRCフォルダー内で、API、構成、コントローラー、ミドルウェア、モデル、およびUTILの次のフォルダーを作成します。
- utilsフォルダー
- 次に、UTILSフォルダープロジェクトに新しいPRIMING.TSファイルを作成して、次のコードを使用して許可を初期化します。
<span>'permitio'から{許可}をインポートします。 </span><span>'../config/environment'から{permit_api_key}をインポートします。 </span><span>//この行はSDKを初期化し、node.jsアプリを接続します </span><span>// Permit.io PDPコンテナへの前のステップで設定します。 </span><span>const permit = new Permit({ </span><span>// APIキー </span> トークン<span>:permit_api_key、// api key in .envに保存 </span><span>//制作中は、展開に合わせてこのURLを変更する必要がある場合があります </span> PDP <span>: 'https://cloudpdp.api.permit.io'、//デフォルトのprimmit.io pdp url </span><span>// SDKにログを放出したい場合は、これを除外してください。 </span> ログ<span>:{ </span> レベル<span>:「デバッグ」、 </span><span>}、 </span><span>//タイムアウト /ネットワークエラーが発生した場合、SDKはfalseを返します </span><span>//代わりにエラーをスローしたい場合は、これを処理させてください。 </span><span>// throwonerror:本当、 </span><span>}); </span> <span>デフォルトの許可をエクスポートします。</span>
このファイルは、node.js用のPermitのSDKを初期化し、環境に保存されているAPIキーを使用してPDPコンテナに接続します。デバッグ用のログを構成し、SDKを設定して、それらをスローするように明示的に構成されていない限り、エラーを黙って処理します。
- 次に、errorhandler.tsというファイルを作成し、次のコードを貼り付けます。
<span>//ユーティリティ関数(例、エラー処理) </span><span>{express 'から{request、response、next -function}をインポートします。 </span> <span>const errorhandler =(err:any、req:request、res:response、next:nextfunction)=> { </span><span>console.error( 'error:'、err.message || err); </span> res <span>.status(err.status || 500).json({ </span> エラー<span>:err.message || '内部サーバーエラー'、 </span><span>}); </span><span>};</span>
このファイルは、エラーを記録し、エラーメッセージとステータスコードを使用してJSON応答を送信するExpressエラー処理ミドルウェアを定義します。特定のステータスが提供されていない場合、デフォルトは500ステータスコードになります。
- モデルフォルダー
- profile.tsというファイルを作成し、次のコードを貼り付けます。
<span>インターフェイスプロファイルをエクスポート{ </span> 名前<span>:文字列; </span> 電子メール<span>:文字列; </span> 役割<span>:「管理者」| 「先生」| '学生'; </span> userid <span>:string; </span><span>}</span>
このファイルは、名前、電子メール、役割、およびユーザーIDのプロパティを含むタイプスクリプトプロファイルインターフェイスを定義します。ここでは、役割は管理、教師、または生徒の特定の値に制限されています。
- assignment.tsファイルを作成し、次のコードを貼り付けます。
<span>'../config/appwrite'から{database、id}をimport {database、id}; </span><span>IMPORT {DATABASE_ID、ASSAVENTS_COLLECTION_ID} from '../config/environment'; </span> <span>インターフェイスAssignmentDataをエクスポート{ </span> タイトル<span>:文字列; </span> 件名<span>:文字列; </span> className <span>:string; </span> 先生<span>:文字列; </span> Duedate <span>:string; </span> creatoremail <span>:string; </span><span>} </span> <span>//新しい割り当てを作成します </span><span>async function createassignmentindb(data:assignmentData)をエクスポートする{ </span><span>return wait database.createdocument( </span><span>database_id、 </span><span>assignments_collection_id、 </span><span>id.unique()、 </span> データ <span>); </span><span>} </span> <span>//すべての割り当てを取得します </span><span>async関数fetchassignmentsfromdb(){{ </span><span>const response = await database.listdocuments(database_id、assainments_collection_id); </span><span>RESPONSE.DOCUMENTSを返します。 </span><span>}</span>
このファイルは、割り当てを管理するためにAppWriteデータベースと対話する機能を提供します。 AssignmentDataインターフェイスを定義し、新しい割り当てを作成し、データベースからすべての割り当てを取得する機能を含みます。
- Student.tsファイルを作成し、次のコードを貼り付けます。
<span>Import {データベース、ID、許可、役割、クエリ} from '../config/appwrite'; </span><span>Import {Database_id、sustent_collection_id} from '../config/environment'; </span> <span>インターフェイスStudentDataをエクスポートする{ </span> FirstName <span>:文字列; </span> lastname <span>:string; </span> 性別<span>:「女の子」| 「少年」| 「少年」| '女の子'; </span> className <span>:string; </span> 年齢<span>:数; </span> creatoremail <span>:string; </span><span>} </span> <span>//新しい学生を作成します </span><span>async関数createStudentindb(data:sustentData)をエクスポートする{ </span><span>return wait database.createdocument( </span><span>database_id、 </span><span>学生_COLLECTION_ID、 </span><span>id.unique()、 </span> データ<span>、 </span><span>[ </span> 許可<span>.Read(role.any())、// public read Permission </span><span>] </span><span>); </span><span>} </span> <span>//すべての生徒を取得します </span><span>async function fetchstudentsfromdb(){{ </span><span>const response = await database.listdocuments(database_id、sustent_collection_id); </span><span>RESPONSE.DOCUMENTSを返します。 </span><span>}</span>
このファイルは、AppWriteデータベース内の学生データを管理する機能を提供します。 StudentDataインターフェイスを定義し、パブリック読み取り権限を持つ新しい学生を作成し、データベースからすべての学生を取得する機能を含みます。
- ミドルウェアフォルダー
- auth.tsファイルを作成し、次のコードを貼り付けます。
<span>{express 'から{request、response、next -function}をインポートします。 </span><span>「JsonWebtoken」からJWTをインポートします。 </span> <span>//リクエストタイプを拡張して「ユーザー」を含める </span><span>インターフェイスAuthentivedRequestはリクエストを拡張します{ </span> ユーザー<span>?:{ </span> ID <span>:文字列; </span> 役割<span>:文字列; </span><span>}; </span><span>} </span> <span>const authmiddleware =(req:authenticedrequest、res:response、next:nextfunction):void => { </span><span>const token = req.headers.authorization?.split( '')[1]; </span> <span>if(!token){ </span> res <span>.status(401).json({error: 'unauthorized。notoken revid'}); </span><span>戻る </span><span>} </span> <span>試す { </span><span>const decoded = jwt.verify(token、process.env.jwt_secret!)as {id:string;)役割:文字列}; </span> req <span>.user = decoded; </span><span>次(); </span><span>} catch(error){ </span> res <span>.status(403).json({error: 'invalid token'}); </span><span>戻る </span><span>} </span><span>}; </span> <span>デフォルトのauthmiddlewareをエクスポートします。</span>
このファイルは、JWTベースの認証のためのExpress Middlewareを定義します。リクエストヘッダーの有効なトークンをチェックし、シークレットキーを使用して検証し、デコードされたユーザー情報(IDおよびロール)をリクエストオブジェクトに添付します。トークンが欠落または無効な場合、適切なエラー応答を返します。
- primmit.tsを作成し、次のコードを貼り付けます。
<span>'../utils/permit'からの輸入許可; </span> <span>const checkusertopermitstudents = async(email:string、action:string、resource:string):promise <boolean> => { </boolean></span><span>試す { </span><span>const const = await birmit.check(email、action、resource); </span><span>console.log( "許可"、許可); </span><span>許可された返品; </span><span>} catch(error){ </span><span>console.error( <span>`erser syncing user <span>$ {email}</span> to permit.io:`、error</span> ); </span><span>falseを返します。 </span><span>} </span><span>}; </span> <span>const const checkusertopermitassignment = async(email:string、action:string、resource:string):promise <boolean> => { </boolean></span><span>試す { </span><span>const const = await birmit.check(email、action、resource); </span><span>console.log( "許可"、許可); </span><span>許可された返品; </span><span>} catch(error){ </span><span>console.error( <span>`erser syncing user <span>$ {email}</span> to permit.io:`、error</span> ); </span><span>falseを返します。 </span><span>} </span><span>};</span>
このファイルは、特定のアクションとリソースの許可証でユーザー許可を確認するために、ユーティリティ関数、CheckUsertOpermitStudents、CheckUsertopermitassignmentを定義します。両方の関数は、エラーを優雅に処理し、問題をログに記録し、許可チェックが失敗した場合にfalseを返します。それらは、アプリケーションの承認を実施するために使用されます。
- コントローラーフォルダー
- auth.tsファイルを作成し、次のコードを貼り付けます。
<span>'../config/appwrite'から{account、id}をimportします。 </span><span>「Express」から{要求、応答}をインポートします。 </span><span>「JsonWebtoken」からJWTをインポートします。 </span> <span>const jwt_secret = process.env.jwt_secret as string; //これが.envファイルに設定されていることを確認してください </span> <span>//サインアップコントローラー </span><span>エクスポートconst signup = async(req:request、res:response)=> { </span><span>const {email、password、name} = req.body; </span> <span>if(!電子メール||!パスワード||!name){ </span><span>REST res.Status(400).JSON({error: '名前、電子メール、パスワードが必要です。'}); </span><span>} </span> <span>試す { </span><span>const user = await account.create(id.unique()、email、password、name); </span><span>// jwtを生成します </span><span>const token = jwt.sign({email}、jwt_secret、{expiresin: '8h'}); </span> res <span>.cookie( 'token'、token、{ </span> httponly <span>:本当、 </span> SAMESITE <span>:「厳格」、 </span> 安全<span>:本当、 </span><span>}); </span> res <span>.status(201).json({success:true、user、token}); </span><span>} catch(error:any){ </span><span>console.error( 'サインアップエラー:'、エラー); </span> res <span>.status(500).json({success:false、message:error.message}); </span><span>} </span><span>}; </span> <span>//ログインコントローラー </span><span>const login = async(req:request、res:response)=> { </span><span>const {email、password} = req.body; </span> <span>if(!電子メール||!パスワード){ </span><span>REST res.Status(400).json({error: '電子メールとパスワードが必要です。'}); </span><span>} </span> <span>試す { </span><span>const session = awaitアカウント。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'、token、{ </span> httponly <span>:本当、 </span> SAMESITE <span>:「厳格」、 </span> 安全<span>:本当、 </span><span>}); </span> res <span>.status(200).json({success:true、token、session}); </span><span>} catch(error:any){ </span><span>console.error( 'ログインエラー:'、エラー); </span> res <span>.status(401).json({success:false、message:error.message}); </span><span>} </span><span>}; </span> <span>//ログアウトコントローラー </span><span>const logout = async(req:request、res:response)=> { </span><span>試す { </span><span>account.deletesession( '現在のセッションID'); </span> res <span>.ClearCookie( 'token'); </span> res <span>.status(200).json({success:true、message: 'logged out out out unted'}); </span><span>} catch(error:any){ </span><span>console.error( 'logout error:'、error); </span> res <span>.status(500).json({success:false、message:error.message}); </span><span>} </span><span>};</span>
このファイルは、サインアップ、ログイン、ログアウト用の認証コントローラーを定義し、ユーザー管理のAppWriteと統合され、セッション処理のためのJWTを統合します。サインアップおよびログインコントローラーは、入力を検証し、ユーザーセッションを作成し、JWTSを生成し、ログアウトコントローラーがセッションとトークンをクリアします。すべてのコントローラーはエラーを処理し、適切な応答を返します。
- assignment.tsファイルを作成し、次のコードを貼り付けます。
<span>「Express」から{要求、応答}をインポートします。 </span><span>Import {createasSignmentIndb、assignmentData、fetchAssignmentsfromdb} from '../models/assignment'; </span><span>'../middleware/permit'から{checkusertopermitassignment}をimport </span> <span>//新しい割り当てを作成します </span><span>Async関数のcreateasSignment(req:request 、res:response):Promise <void> { </void></span><span>試す { </span><span>const {title、件名、教師、classname、duedate、creatoremail}:assignmentdata = req.body; </span> <span>const ispermitted = await checkusertopermitassignment(createoremail、 "create"、 "assionments"); </span><span>if(!ispermitted){ </span> res <span>.status(403).json({error: 'authoritized'}); </span><span>戻る; </span><span>} </span> <span>const newAssignment = await createassignmentindb({ </span> タイトル<span>、 </span> 主題<span>、 </span> 教師<span>、 </span> className <span>、 </span> 期日<span>、 </span> CreatorEmail <span>}); </span> <span>console.log( '新しい割り当て作成:'、newAssignment); </span> res <span>.status(201).json(newassignment); </span><span>} catch(error){ </span><span>console.error( 'assionalの作成エラー:'、エラー); </span> res <span>.status(500).json({error:(as As Any).message}); </span><span>} </span><span>} </span> <span>//すべての割り当てを取得します </span><span>Async関数FetchAssignments(req:request、res:response)をエクスポート:Promise <void> { </void></span><span>試す { </span><span>const {email} = req.params; </span> <span>const ispermitted = await checkusertopermitassignment(email、 "read"、 "assignments"); </span><span>if(!ispermitted){ </span> res <span>.status(403).json({message: 'not authorized'}); </span><span>戻る; </span><span>} </span> <span>const assignments = await fetchassignmentsfromdb(); </span> res <span>.status(200).json(課題); </span><span>} catch(error){ </span> res <span>.status(500).json({error:(as As Any).message}); </span><span>} </span><span>}</span>
このファイルは、データベースと統合するための割り当てを作成およびフェッチするためのコントローラーを定義し、許可チェックの許可を与えます。 createasSignmentコントローラーは、入力を検証し、アクセス許可をチェックし、新しい割り当てを作成しますが、FetchAssignmentsコントローラーはアクセスを確認した後、すべての割り当てを取得します。両方のコントローラーはエラーを処理し、適切な応答を返します。
- Student.tsファイルを作成し、次のコードを貼り付けます。
<span>輸入 { </span> createStudentindb <span>、 </span> FetchStudentsFromDB <span>、 </span> StudentData <span>} from '../models/student'; </span><span>「Express」から{要求、応答}をインポートします。 </span><span>'../middleware/permit'から{checkusertopermitstudents}をインポート </span> <span>Async関数createStudent(req:request、res:response)をエクスポート:Promise <void> { </void></span><span>試す { </span><span>const {firstName、lastName、gender、classname、age、creatoremail}:sustentdata = req.body; </span> <span>if(!['girl'、 'boy']。include(gender)){ </span> res <span>.status(400).json({error: '無効な性別タイプ'}); </span><span>戻る; </span><span>} </span> <span>const ispermitted = await checkusertopermitstudents(creatoremail、 "create"、 "sustent"); </span><span>if(!ispermitted){ </span> res <span>.status(403).json({message: 'not authorized'}); </span><span>戻る; </span><span>} </span> <span>const newStudent = await createStudentindb({ </span> ファーストネーム<span>、 </span> 苗字<span>、 </span> 性別<span>、 </span> className <span>、 </span> 年<span>、 </span> CreatorEmail <span>}); </span> res <span>.status(201).json(NewStudent); </span><span>} catch(error){ </span> res <span>.status(500).json({error:(as As Any).message}); </span><span>} </span><span>} </span> <span>//すべての生徒を取得します </span><span>Async関数FetchStudents(REQ:Request、Res:Response)をエクスポート:Promise <void> { </void></span><span>試す { </span><span>const {email} = req.params; </span> <span>const ispermitted = await checkusertopermitstudents(email、 "read"、 "sustent"); </span><span>if(!ispermitted){ </span> res <span>.status(403).json({message: 'not authorized'}); </span><span>戻る; </span><span>} </span> <span>const desustor = await fetchstudentsfromdb(); </span> res <span>.status(200).json(学生); </span><span>} catch(error){ </span> res <span>.status(500).json({error:(as As Any).message}); </span><span>} </span><span>}</span>
このファイルは、生徒を作成および取得するためのコントローラーを定義し、データベースと統合され、許可チェックの許可を定義します。 CreateStudent Controllerは、入力を検証し、許可をチェックし、新しい学生を作成しますが、FetchStudents Controllerはアクセスを確認した後、すべての学生を取得します。両方のコントローラーはエラーを処理し、適切な応答を返します。
- profile.tsファイルを作成し、次のコードを貼り付けます。
<span>'@/models/profile'から{profile}をインポートします。 </span><span>「axios」からaxiosをインポートします。 </span><span>'../config/appwrite'から{database、id、query}をimportします。 </span><span>{express 'から{request、response、nextfunction、requesthandler}をインポートします。 </span><span>'../config/environment'から{permit_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 environmentId = process.env.permit_env_id as string </span> <span>const permit_api_url = <span>`https://api.permit.io/v2/facts/ <span>$ {projectid}</span> / <span>$ {environmentId}</span> /users`</span> ; </span><span>const permit_auth_header = { </span> 承認<span>: <span>`BEARER <span>$ {permit_api_key}</span> `</span> 、 </span><span>「コンテンツタイプ」:「アプリケーション/json」、 </span><span>}; </span> <span>//プロファイルコントローラーを作成します </span><span>const createprofileをエクスポート:requesthandler = async(req:request、res:response、next:nextfunction):promise <void> => { </void></span><span>const {firstName、lastName、email、role、userid} = req.body; </span><span>console.log(req.body); </span> <span>if(!email ||!role ||!userid){ </span> res <span>.status(400).json({error: 'firstName、lastName、email、role、およびuserIdが必要です。'}); </span><span>戻る; </span><span>} </span> <span>//役割を検証します </span><span>const adoladRoles:profile ['role'] [] = ['admin'、 'teacher'、 'sustent']; </span><span>if(!appodRoles.Includes(role)){ </span> res <span>.status(400).json({error: '無効な役割。許可役割:管理者、教師、生徒'}); </span><span>戻る; </span><span>} </span> <span>試す { </span><span>const newuser = await database.createdocument( </span> DatabaseID <span>、 </span> profileid <span>、 </span><span>id.unique()、 </span><span>{firstName、lastName、電子メール、役割、userid} </span><span>); </span><span>//ステップ2:comprit.ioにユーザーを同期します </span><span>const permitPayload = { </span> キー<span>:メール、 </span> メール<span>、 </span> first_name <span>:firstName、 </span> last_name <span>:lastname、 </span> role_assignments <span>:[{role、tenant: "default"}]、 </span><span>}; </span> <span>許可してみましょう。 </span><span>試す { </span><span>const response = await axios.post(permit_api_url、permitpayload、{headers:permit_auth_header}); </span> permitResponse <span>= response.data; </span><span>console.log( "commit.io:"、permitresponse)に同期したユーザー); </span><span>} catch(permiterror){ </span><span>if(axios.isaxioserror(permiterror)){ </span><span>console.error( "commit.io:"、permitError.response?.data || permiterror.message)をcomprimit.ioに同期できなかった。 </span><span>} それ以外 { </span><span>console.error( "comprid.io:"、permiterror)をcompridingに同期できなかった); </span><span>} </span> permitResponse <span>= {error: "permit.io"}と同期できなかった。 </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(error:any){ </span> res <span>.status(500).json({success:false、message:error.message}); </span><span>戻る; </span><span>} </span><span>}; </span> <span>//メールでプロファイルを取得します </span><span>Export const getProfileByemail = async(req:request、res:response、next:nextfunction):promise <void> => { </void></span><span>const {email} = req.params; </span> <span>if(!email){ </span> res <span>.status(400).json({error: '電子メールが必要です。'}); </span><span>戻る; </span><span>} </span> <span>試す { </span><span>const profile = await database.listdocuments( </span> DatabaseID <span>、 </span> profileid <span>、 </span><span>[query.equal( "email"、email)] </span><span>); </span> <span>if(profile.documents.length === 0){ </span> res <span>.status(404).json({error: 'プロファイルが見つかりません'}); </span><span>戻る; </span><span>} </span> res <span>.status(200).json({success:true、profile:profile.documents [0]}); </span><span>} catch(error:any){ </span><span>console.error( 'エラーの取得プロファイル:'、エラー); </span> res <span>.status(500).json({success:false、message:error.message}); </span><span>} </span><span>};</span>
このファイルは、ユーザープロファイルを作成およびフェッチするためのコントローラーを定義し、データベース操作のためのAppWriteと統合され、役割の同期の許可を定義します。 createProfileコントローラーは、入力を検証し、プロファイルを作成し、ユーザーが許可するように同期しますが、GetProfileEmailコントローラーは電子メールでプロファイルを取得します。両方のコントローラーはエラーを処理し、適切な応答を返します。
- 構成フォルダー
- appwrite.tsファイルを作成し、次のコードを貼り付けます。
<span>Import {クライアント、アカウント、データベース、ストレージ、ID、許可、役割、クエリ} from 'appwrite'; </span><span>Import {appwrite_endpoint、appwrite_project_id、appwrite_api_key} from './environment'; </span> <span>// appwriteクライアントを初期化します </span><span>const client = new Client() </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>(任意のクライアント).config.key = appwrite_api_key; // APIキーを設定するための回避策 </span><span>} </span> <span>// appwriteサービスを初期化します </span><span>const Account = new Account(client); </span><span>const database = new Database(client); </span><span>const Storage = new Storage(client); </span> <span>// appwriteクライアントとサービスをエクスポートします </span><span>Export {クライアント、アカウント、データベース、ストレージ、ID、許可、役割、クエリ};</span>
このファイルは、プロジェクトエンドポイント、ID、およびオプションのAPIキーを使用して、AppWriteクライアントを初期化および構成します。また、ID、許可、役割、クエリなどのユーティリティ定数とともに、アカウント、データベース、ストレージなどのAppWriteサービスをセットアップおよびエクスポートします。
- 環境を作成してファイルを作成し、次のコードを貼り付けます。
<span>「dotenv」からdotenvをインポートします。 </span>dotenv <span>.config(); // .ENVから環境変数をロードします </span> <span>const const appwrite_endpoint = process.env.appwrite_endpoint || ''; </span><span>const const permit_api_key = process.env.permit_api_key || ''; </span><span>const const permit_project_id = process.env.permit_project_id || ''; </span><span>const const permit_env_id = process.env.permit_env_id || ''; </span><span>const const appwrite_project_id = process.env.appwrite_project_id || ''; </span><span>const database_id = process.env.appwrite_database_id ||をエクスポートします''; </span><span>const desustom_collection_id = process.env.appwrite_students_collection_id || ''; </span><span>const assuctments_collection_id = process.env.appwrite_assignments_collection_id || ''; </span> <span>const profile_collection_id = process.env.appwrite_profile_collection_id || '';</span>
このファイルは、A.ENVファイルから環境変数をロードし、AppWriteやPermitの構成、データベースID、および収集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>router <span>.post( '/desustor'、authmiddleware、createStudent); //新しい学生を作成します </span>router <span>.get( '/sustent/:email'、authmiddleware、fetchStudents); //すべての生徒を取得します </span><span>デフォルトルーターをエクスポートします。 //ルーターインスタンスをエクスポートします</span>
このファイルは、学生データを管理するためのエンドポイントを備えたExpressルーターをセットアップします。これには、認証ミドルウェア(authmiddleware)によって保護されている新しい学生を作成し、生徒を取得するためのルートが含まれています。次に、アプリケーションで使用するためにルーターがエクスポートされます。
- auth.tsファイルを作成し、次のコードを貼り付けます。
<span>// src/routes/authreoutes.ts </span><span>「Express」からExpressをインポートします。 </span><span>'../controllers/auth'から{signup、login、logout}をインポートします。 </span> <span>const router = express.router(); </span> <span>// AUTH関連のエンドポイントを定義します </span>router <span>.post( '/signup'、(req、res、next)=> {//サインアップルート </span><span>サインアップ(req、res).then(()=> { </span><span>次(); </span><span>})。catch((err)=> { </span><span>next(err); </span><span>}); </span><span>}); </span>router <span>.post( '/login'、(req、res、next)=> {//ログインルート </span><span>login(req、res).then(()=> { </span><span>次(); </span><span>})。catch((err)=> { </span><span>next(err); </span><span>}); </span><span>}); </span>router <span>.post( '/logout'、logout); //ログアウトルート </span><span>デフォルトルーターをエクスポートします。 //ルーターインスタンスをエクスポートします</span>
このファイルは、ユーザーのサインアップ、ログイン、ログアウトなど、認証関連のアクション用のエンドポイントを備えたExpressルーターをセットアップします。サインアップおよびログインルートは、エラー処理で非同期操作を処理しますが、ログアウトルートは簡単です。ルーターは、アプリケーションで使用するためにエクスポートされます。
- assignment.tsファイルを作成し、次のコードを貼り付けます。
<span>「Express」からのインポートエクスプレス </span><span>"../controllers/assignment"から{createassignment、fetchassignments}をimport </span><span>"../middleware/auth"からauthmiddlewareをインポートする </span> <span>const router = express.router() </span> router <span>.post( "/create"、authmiddleware、createassignment) </span>router <span>.get( "/:email"、authmiddleware、fetchassignments) </span><span>デフォルトルーターをエクスポートします</span>
このファイルは、割り当てを管理するためのエンドポイントを備えたExpressルーターをセットアップします。これには、割り当てを作成し、割り当てを取得するためのルートが含まれています。どちらも認証ミドルウェア(AuthMiddleware)によって保護されています。ルーターは、アプリケーションで使用するためにエクスポートされます。
- profile.tsファイルを作成し、次のコードを貼り付けます。
<span>「Express」からExpressをインポートします。 </span><span>Import {createProfile、getProfileByemail} from '../controllers/profile'; </span><span>'../middleware/auth'からauthmiddlewareをインポートします。 </span> <span>const router = express.router(); </span> <span>//プロファイルを作成するためのルート </span>router <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>// Middleware </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 中国語 Web サイトの他の関連記事を参照してください。

さまざまなJavaScriptエンジンは、各エンジンの実装原則と最適化戦略が異なるため、JavaScriptコードを解析および実行するときに異なる効果をもたらします。 1。語彙分析:ソースコードを語彙ユニットに変換します。 2。文法分析:抽象的な構文ツリーを生成します。 3。最適化とコンパイル:JITコンパイラを介してマシンコードを生成します。 4。実行:マシンコードを実行します。 V8エンジンはインスタントコンピレーションと非表示クラスを通じて最適化され、Spidermonkeyはタイプ推論システムを使用して、同じコードで異なるパフォーマンスパフォーマンスをもたらします。

現実世界におけるJavaScriptのアプリケーションには、サーバー側のプログラミング、モバイルアプリケーション開発、モノのインターネット制御が含まれます。 2。モバイルアプリケーションの開発は、ReactNativeを通じて実行され、クロスプラットフォームの展開をサポートします。 3.ハードウェアの相互作用に適したJohnny-Fiveライブラリを介したIoTデバイス制御に使用されます。

私はあなたの日常的な技術ツールを使用して機能的なマルチテナントSaaSアプリケーション(EDTECHアプリ)を作成しましたが、あなたは同じことをすることができます。 まず、マルチテナントSaaSアプリケーションとは何ですか? マルチテナントSaaSアプリケーションを使用すると、Singの複数の顧客にサービスを提供できます

この記事では、許可によって保護されたバックエンドとのフロントエンド統合を示し、next.jsを使用して機能的なedtech SaaSアプリケーションを構築します。 FrontEndはユーザーのアクセス許可を取得してUIの可視性を制御し、APIリクエストがロールベースに付着することを保証します

JavaScriptは、現代のWeb開発のコア言語であり、その多様性と柔軟性に広く使用されています。 1)フロントエンド開発:DOM操作と最新のフレームワーク(React、Vue.JS、Angularなど)を通じて、動的なWebページとシングルページアプリケーションを構築します。 2)サーバー側の開発:node.jsは、非ブロッキングI/Oモデルを使用して、高い並行性とリアルタイムアプリケーションを処理します。 3)モバイルおよびデスクトップアプリケーション開発:クロスプラットフォーム開発は、反応および電子を通じて実現され、開発効率を向上させます。

JavaScriptの最新トレンドには、TypeScriptの台頭、最新のフレームワークとライブラリの人気、WebAssemblyの適用が含まれます。将来の見通しは、より強力なタイプシステム、サーバー側のJavaScriptの開発、人工知能と機械学習の拡大、およびIoTおよびEDGEコンピューティングの可能性をカバーしています。

JavaScriptは現代のWeb開発の基礎であり、その主な機能には、イベント駆動型のプログラミング、動的コンテンツ生成、非同期プログラミングが含まれます。 1)イベント駆動型プログラミングにより、Webページはユーザー操作に応じて動的に変更できます。 2)動的コンテンツ生成により、条件に応じてページコンテンツを調整できます。 3)非同期プログラミングにより、ユーザーインターフェイスがブロックされないようにします。 JavaScriptは、Webインタラクション、シングルページアプリケーション、サーバー側の開発で広く使用されており、ユーザーエクスペリエンスとクロスプラットフォーム開発の柔軟性を大幅に改善しています。

Pythonはデータサイエンスや機械学習により適していますが、JavaScriptはフロントエンドとフルスタックの開発により適しています。 1. Pythonは、簡潔な構文とリッチライブラリエコシステムで知られており、データ分析とWeb開発に適しています。 2。JavaScriptは、フロントエンド開発の中核です。 node.jsはサーバー側のプログラミングをサポートしており、フルスタック開発に適しています。


ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

ドリームウィーバー CS6
ビジュアル Web 開発ツール

Safe Exam Browser
Safe Exam Browser は、オンライン試験を安全に受験するための安全なブラウザ環境です。このソフトウェアは、あらゆるコンピュータを安全なワークステーションに変えます。あらゆるユーティリティへのアクセスを制御し、学生が無許可のリソースを使用するのを防ぎます。

EditPlus 中国語クラック版
サイズが小さく、構文の強調表示、コード プロンプト機能はサポートされていません

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

WebStorm Mac版
便利なJavaScript開発ツール
