


Construire une application SaaS multi-locataire avec next.js (intégration backend)
J'ai construit une application SAAS multi-locataire fonctionnelle (une application EdTech) avec votre outil technologique quotidien et vous pouvez faire de même.
Tout d'abord, qu'est-ce qu'une application SaaS multi-locataire?
Les applications SAAS multi-locataires vous permettent de servir plusieurs clients à partir d'une seule base de code. Mais pour ce faire, vous devrez gérer un accès sécurisé et spécifique aux locataires, et cela peut être difficile lorsqu'il est fait manuellement. C'est pourquoi j'ai décidé d'utiliser le permis, un outil d'autorisation moderne qui simplifie ce processus.
Dans cet article, je vais vous montrer comment simplifier l'autorisation de vos applications SaaS en utilisant le permis, avec un exemple étape par étape de création d'une application de démonstration avec l'isolement des locataires et le contrôle d'accès basé sur les rôles (RBAC) avec Next.js et AppWrite.
Que sont les prochains.js et les approvisionnements, et pourquoi en avons-nous besoin?
Next.js
Next.js est un framework basé sur React qui fournit un rendu côté serveur (SSR), une génération de sites statiques (SSG), des itinéraires API et des optimisations de performances hors de la boîte.
Pour ce projet, j'ai utilisé Suivant.js car:
- Il permet le pré-intention des pages, ce qui améliore les performances et le référencement.
- Son routage intégré facilite la gestion des transitions de page et du contenu dynamique.
- Il s'intègre facilement aux services backend tels que AppWrite et Permit.io pour l'authentification et l'autorisation.
Approuver des applications
AppWrite est une plate-forme backend-as-a-Service (BAAS) qui fournit des fonctions d'authentification, de données, de stockage et de serveurs des utilisateurs. L'utilisation d'un service comme AppWrite élimine la nécessité de construire un backend à partir de zéro, afin que vous puissiez vous concentrer sur le développement du frontend tout en ayant accès aux capacités du backend.
Pour ce projet, j'ai utilisé AppWrite:
- Pour gérer l'enregistrement des utilisateurs, la connexion et la gestion des sessions.
- Pour fournir une base de données NOSQL structurée pour stocker des données spécifiques aux locataires.
L'utilisation de Next.js et AppWrite m'a permis de créer une application SaaS multi-locataire évolutive et haute performance tout en gardant le processus de développement efficace.
Introduction à l'autorisation du SaaS multi-locataire
Une application SaaS multi-locataire est un logiciel qui sert plusieurs utilisateurs ou groupes d'utilisateurs, appelés locataires, en utilisant une instance logicielle unique de l'application.
Cela signifie que dans une architecture SaaS multi-locataires, plusieurs clients (locataires) partagent la même infrastructure d'application ou utilisent la même application mais maintiennent l'isolement des données.
Un exemple pratique de ceci est un outil de gestion de projet comme Trello.
- Il s'agit d'une seule infrastructure qui s'exécute sur des serveurs partagés et a la même base de code pour tous ses utilisateurs.
- Chaque entreprise utilisant Trello (par exemple, la société A et la société B) est un locataire.
- Il isole les données:
- Les employés de l'entreprise A ne peuvent voir que leurs projets, tâches et conseils.
- Les employés de la société B ne peuvent pas accéder ou consulter les données de l'entreprise A, et vice versa.
Cela garantit que si les ressources sont partagées, les données et activités de chaque locataire sont privées et sécurisées.
Dans une application multi-locataire, même au sein d'un locataire, certains utilisateurs auront un accès plus élevé à certaines informations, tandis que certains membres seront limités à certaines ressources.
L'autorisation dans ces applications doit:
- Assurez-vous que les utilisateurs ne peuvent pas accéder aux données ou aux ressources des autres locataires ou des clients. C'est ce qu'on appelle les locataires isolants.
- Assurez-vous que les utilisateurs au sein d'un locataire peuvent accéder uniquement aux ressources que leurs rôles permettent en fournissant un contrôle d'accès granulaire.
- Gérer plus d'utilisateurs, de locataires et de rôles sans ralentir ou dégrader les performances.
Importance de l'isolement des locataires et du contrôle d'accès granulaire
L'isolement des locataires maintient les données en garantissant que les informations de chaque client restent privées. Bien que le contrôle d'accès granulaire garantit que les utilisateurs au sein d'une organisation n'obtiennent que les autorisations dont ils ont besoin.
La mise en œuvre de l'autorisation dans vos applications SaaS peut être complexe et délicate, mais ce n'est pas nécessaire lorsque vous avez un outil d'autorisation comme le permis.
Quel est le permis et quels sont ses avantages?
Le permis est un outil d'autorisation facile à utiliser pour gérer l'accès dans toute application, y compris les applications multi-locataires. L'utilisation de permis.io dans votre application vous permet de définir facilement et d'attribuer des rôles avec des autorisations spécifiques pour le contrôle d'accès dans votre application. En plus de créer des rôles dans l'application, vous pouvez également ajouter des conditions et des règles en fonction des attributs de l'utilisateur ou de ressource pour spécifier ce que chaque utilisateur peut et ne peut pas faire.
Maintenant que vous connaissez la plupart de ce que vous devez savoir sur le permis et ses avantages, passons dans l'accord principal: construire une demande SaaS avec Next.js et permis d'intégration pour l'autorisation.
Pour démontrer la puissance du permis, nous allons construire une plate-forme Multi-locataire EdTech SaaS.
La création d'une plate-forme EdTech SaaS implique plusieurs défis, notamment l'authentification des utilisateurs, le contrôle d'accès basé sur les rôles (RBAC) et la multi-location. Nous utiliserons Next.js pour le frontend, AppWrite pour l'authentification et la gestion de la base de données, et permettre une autorisation à grains fins.
Présentation de la pile technologique
Architecture du système
L'application suit une approche en premier backend:
- Backend (node.js express)
- Gère les demandes de l'API et la logique métier.
- Utilise AppWrite pour l'authentification et la gestion des bases de données.
- Implémente le permis pour l'autorisation, la définition des rôles et des autorisations.
- S'assure que chaque demande est validée avant l'accès aux données.
- Frontend (next.js)
- Se connecte au backend pour récupérer en toute sécurité les données.
- Utilise le rendu de l'interface utilisateur basé sur les rôles, ce qui signifie que les utilisateurs ne voient que ce qu'ils sont autorisés à accéder.
- Restreint les actions (comme créer des affectations) en fonction des autorisations.
En appliquant l'autorisation au niveau de l'API, nous nous assurons que les utilisateurs ne peuvent pas contourner les restrictions, même s'ils manipulent le frontend.
À la fin de ce guide, vous aurez une application EdTech SAAS multi-locataire entièrement fonctionnelle, où:
- Les administrateurs peuvent ajouter et voir les étudiants.
- Les enseignants peuvent ajouter et voir les élèves, ainsi que créer des devoirs.
- Les étudiants ne peuvent voir que leurs cours assignés.
Cet article fournit une ventilation étape par étape de la façon dont j'ai mis en œuvre le permis pour gérer l'autorisation de construire ce projet, alors suivez et construisez le vôtre.
Mise en œuvre du backend avec permis
Pour appliquer le contrôle d'accès basé sur les rôles (RBAC) et l'isolement des locataires, nous devons:
- Mettre en place un permis et définir des rôles, des locataires et des politiques.
- Intégrer le permis dans le backend (Node.js Express).
- Protégez les routes de l'API à l'aide de middleware qui vérifie les autorisations avant d'autoriser les demandes.
Allons étape par étape.
1. Permis de configuration
Avant d'écrire un code, vous devez
- Créez un compte sur le permis.

Vous serez présenté avec l'intégration, mais une fois que vous entrerez le nom de votre organisation, vous pouvez simplement sauter la configuration.
- Créer une ressource et des actions
Accédez à la section de stratégie, où vous créerez une ressource et des actions que vous pouvez effectuer sur cette ressource.

Une fois que vous avez terminé la création de vos ressources, cela devrait ressembler à ceci:

- Créer un rôle
Après avoir créé les ressources, accédez à la page Rôles à l'aide de l'onglet Rôles. Vous verrez que certains rôles ont été automatiquement attribués.

Supprimez ces rôles et créez de nouveaux rôles. Chaque rôle aura des règles spécifiques qui lui sont associées, sur ce qu'un utilisateur peut et ne peut pas faire. Créez d'abord le rôle d'administrateur, car il servira plus tard de bloc de construction pour les conditions RBAC. Cliquez sur le bouton Ajouter un rôle en haut et créez les rôles.

Lorsque vous avez terminé de créer vos rôles, cela devrait ressembler à ceci:

Super!
Maintenant que vous avez créé vos ressources et vos rôles, vous pouvez désormais configurer des autorisations dans l'éditeur de stratégie.
- Configuration des autorisations dans l'éditeur de stratégie
Revenez à l'éditeur de politique et c'est à quoi ressemblera les rôles maintenant, avec chaque ressource individuelle définie et les actions que vous pouvez sélectionner. Vous êtes maintenant prêt à donner des autorisations aux rôles pour effectuer les actions sélectionnées sur la ressource.

Lorsque vous avez terminé la sélection des actions pour chaque rôle, cliquez sur le bouton Enregistrer les modifications en bas à droite de la page.
- Copier les clés de l'API
Enfin, pour utiliser le Cloud PDP de permis, vous aurez besoin de la clé API de votre environnement actuel. Pour ce projet, vous allez utiliser la clé d'environnement de développement. Passez aux paramètres et cliquez sur les touches de l'API, faites défiler vers le bas vers les touches API environnement, cliquez sur «Revever Key», puis copiez-le.

Après avoir configuré votre tableau de bord de permis, vous pouvez maintenant passer à votre backend.
2. Installation des dépendances
Pour commencer, vous devrez installer Node.js sur votre ordinateur. Une fois que Node.js est installé sur votre système, suivez ces étapes:
- Commencez par créer un nouveau projet en utilisant les commandes suivantes:
MKDIR Backend cd backendnpm init -y
- Ensuite, installez les packages suivants:
NPM Install Express Dotenv pertio COR
- Configurer le permis dans Express. Dans votre fichier .env, stockez votre clé API:
Permut_api_key = your-permit-key-you-copied-inarlier
3. Configuration de AppWrite
- Accédez à AppWrite et créez un nouveau projet en saisissant un nom de projet et en sélectionnant une région. Notez votre ID de projet et votre point de terminaison API; C'est ce que vous entrerez en tant que valeurs dans votre fichier .env. Votre fichier Env devrait ressembler à ceci:
Permut_api_key = your-permit-key-you-copied-inarlier Appwrite_endpoint = https: //cloud.appwrite.io/v1 Appwrite_project_id = votre projection-id
- Procédez maintenant aux bases de données pour créer votre base de données, puis copiez votre ID de base de données pour le coller dans votre fichier env.

Votre fichier Env devrait maintenant ressembler à ceci:
Permut_api_key = your-permit-key-you-copied-inarlier Appwrite_endpoint = https: //cloud.appwrite.io/v1 Appwrite_project_id = votre projection-id Appwrite_database_id = votre database-id
Créez maintenant les collections suivantes dans la base de données AppWrite avec les attributs suivants:
- Collection des profils

- Collection des étudiants

- Collection des affectations

À quoi devrait ressembler votre fichier Env à ce stade:
Permut_api_key = your-permit-key-you-copied-inarlier Permut_project_id = copier-from-dashboard Permut_env_id = copier-from-dashboard Appwrite_endpoint = https: //cloud.appwrite.io/v1 Appwrite_project_id = votre projection-id Appwrite_database_id = votre database-id Appwrite_profile_collection_id = your-id Appwrite_assignments_collection_id = your-id Appwrite_students_collection_id = votre-id JWT_SECRET = Générer-This-By-Running // OpenSSL Rand -Base64 16 Port = 8080
4. Créer une structure de fichiers et des fichiers
Créez maintenant un dossier SRC dans la racine du fichier. Générez ensuite le fichier tsconfig.json dans le dossier racine et collez le code suivant:
<span>{ </span><span>"CompilerOptions": { </span><span>"cible": "es6", </span><span>"Module": "CommonJs", </span><span>"outdir": "./dist", </span><span>"esmoduleInterop": true, </span><span>"ForceConsistentCasingInFilenames": true, </span><span>"Strict": vrai, </span><span>"skiplibcheck": vrai, </span><span>"ResololjsonModule": true, </span><span>"BUSURL": "./", </span><span>"chemins": { </span><span>"@ / *": ["src / *"] </span><span>} </span><span>}, </span><span>"inclure": ["src / ** / *"], </span><span>"exclure": ["node_modules", "dist"] </span><span>}</span>
Ce tsconfig.json configure le compilateur TypeScript pour cibler ES6, utiliser les modules CommonJS et les fichiers de sortie sur ./dist. Il applique la vérification stricte du type, permet la résolution du module JSON, configure les alias de chemin pour SRC et exclut Node_Modules et DIST à partir de la compilation.
À l'intérieur du dossier SRC, créez les dossiers suivants: API, config, contrôleurs, middleware, modèles et utils.
- Folder Utils
- Maintenant, créez un nouveau fichier permis.ts dans le projet de dossier UTILS pour initialiser le permis en utilisant le code suivant:
<span>Import {permis} de 'pertiatio'; </span><span>import {permis_api_key} de '../config/environment'; </span><span>// Cette ligne initialise le SDK et connecte votre application Node.js </span><span>// Dans le conteneur Permit.io PDP que vous avez configuré à l'étape précédente. </span><span>const permis = nouveau permis ({ </span><span>// Votre clé API </span> Token <span>: permis_api_key, // Stockez votre clé API dans .env </span><span>// En production, vous devrez peut-être modifier cette URL pour s'adapter à votre déploiement </span> PDP <span>: 'https://cloudpdp.api.permit.io', // URL PDU PERMER.IO PDP </span><span>// Si vous voulez que le SDK émet des journaux, décomancement ceci: </span> enregistrer <span>: { </span> Niveau <span>: "débogage", </span><span>}, </span><span>// Le SDK renvoie False si vous obtenez une erreur de délai / réseau </span><span>// Si vous voulez qu'il lance une erreur à la place, et vous laissez gérer ceci, décommentez ceci: </span><span>// ThrowonError: vrai, </span><span>}); </span> <span>Exporter le permis par défaut;</span>
Ce fichier initialise le SDK du permis pour node.js, le connectant au conteneur PDP du permis à l'aide d'une clé API stockée dans l'environnement. Il configure la journalisation pour le débogage et configure le SDK pour gérer les erreurs silencieusement à moins de configurer explicitement pour les lancer.
- Ensuite, créez un fichier appelé errorhandler.ts et collez le code suivant:
<span>// fonctions utilitaires (par exemple, gestion des erreurs) </span><span>import {request, réponse, nextFunction} de 'express'; </span> <span>export const errorhandler = (err: any, req: request, res: réponse, suivant: nextFunction) => { </span><span>Console.Error ('Erreur:', err.Message || err); </span> res <span>.status (err.status || 500) .json ({ </span> Erreur <span>: err.message || 'Erreur interne du serveur', </span><span>}); </span><span>};</span>
Ce fichier définit un middleware exprimé de gestion des erreurs qui enregistre les erreurs et envoie une réponse JSON avec le message d'erreur et le code d'état. Il est par défaut un code d'état de 500 si aucun statut spécifique n'est fourni.
- Dossier de modèles
- Créez un fichier appelé Profil.ts et collez le code suivant:
<span>Profil d'interface d'exportation { </span> Nom <span>: String; </span> Email <span>: chaîne; </span> Rôle <span>: «Admin» | 'Professeur' | 'Étudiant'; </span> userId <span>: String; </span><span>}</span>
Ce fichier définit une interface de profil TypeScript avec des propriétés pour le nom, l'e-mail, le rôle et l'utilisateur, où le rôle est limité à des valeurs spécifiques: administrateur, enseignant ou élève.
- Créer un fichier d'affectation.ts et collez le code suivant:
<span>import {database, id} de '../config/appwrite'; </span><span>import {database_id, affectations_collection_id} depuis '../config/environment'; </span> <span>Exportation d'interface AffectationData { </span> Titre <span>: String; </span> Sujet <span>: String; </span> ClassName <span>: String; </span> enseignant <span>: chaîne; </span> DUeate <span>: String; </span> Creatoremail <span>: String; </span><span>} </span> <span>// Créer une nouvelle affectation </span><span>Exporter la fonction asynchrone createAsSignmentIndB (data: affectmentData) { </span><span>return Await Database.CreateDocument ( </span><span>Database_id, </span><span>Affectations_collection_id, </span><span>Id.Unique (), </span> données <span>)); </span><span>} </span> <span>// Récupérer toutes les affectations </span><span>Exportation de la fonction asynchrone fetchAssignmentsFromdb () { </span><span>const Response = Await Database.ListDocuments (database_id, affectations_collection_id); </span><span>Retour Response.Documents; </span><span>}</span>
Ce fichier fournit des fonctions pour interagir avec une base de données AppWrite pour gérer les affectations. Il définit une interface d'affectationData et inclut des fonctions pour créer une nouvelle affectation et récupérer toutes les affectations à partir de la base de données.
- Créez un fichier student.ts et collez le code suivant:
<span>import {database, id, permission, rôle, requête} de '../config/appwrite'; </span><span>import {database_id, students_collection_id} de '../config/environment'; </span> <span>Interface d'exportation StudentData { </span> FirstName <span>: String; </span> LastName <span>: String; </span> Genre <span>: «fille» | 'Boy' | 'Boy' | 'Fille'; </span> ClassName <span>: String; </span> Âge <span>: nombre; </span> Creatoremail <span>: String; </span><span>} </span> <span>// Créer un nouvel étudiant </span><span>Exporter la fonction asynchrone CreateStudentindB (data: StudentData) { </span><span>return Await Database.CreateDocument ( </span><span>Database_id, </span><span>Students_Collection_id, </span><span>Id.Unique (), </span> données <span>, </span><span>[ </span> Permission <span>.Read (Role.Any ()), // Permission de lecture publique </span><span>]] </span><span>)); </span><span>} </span> <span>// chercher tous les étudiants </span><span>Exporter la fonction asynchrone fetchstuentsfromdb () { </span><span>const Response = Await Database.ListDocuments (Database_ID, Students_Collection_ID); </span><span>Retour Response.Documents; </span><span>}</span>
Ce fichier fournit des fonctions pour gérer les données des étudiants dans une base de données AppWrite. Il définit une interface StudentData et comprend des fonctions pour créer un nouvel étudiant avec des autorisations de lecture publique et récupérer tous les étudiants de la base de données.
- Dossier de middleware
- Créer un fichier auth.ts et coller le code suivant:
<span>import {request, réponse, nextFunction} de 'express'; </span><span>importer JWT de «jsonwebtoken»; </span> <span>// étendre le type de demande pour inclure «utilisateur» </span><span>Interface AuthenticatedRequest étend la demande { </span> utilisateur <span>?: { </span> id <span>: chaîne; </span> Rôle <span>: String; </span><span>}; </span><span>} </span> <span>const authmiddleware = (req: authenticatedRequest, res: réponse, suivant: nextFunction): void => { </span><span>const token = req.heders.authorisation? .split ('') [1]; </span> <span>if (! Token) { </span> res <span>.status (401) .json ({error: 'non autorisé. Non token fourni'}); </span><span>retour </span><span>} </span> <span>essayer { </span><span>const decoded = jwt.verify (token, process.env.jwt_secret!) as {id: string; rôle: String}; </span> req <span>.User = décodé; </span><span>suivant(); </span><span>} catch (erreur) { </span> res <span>.status (403) .json ({error: 'invalid token'}); </span><span>retour </span><span>} </span><span>}; </span> <span>Exporter par défaut authmiddleware;</span>
Ce fichier définit un middleware express pour l'authentification basée sur JWT. Il vérifie un jeton valide dans l'en-tête de demande, le vérifie à l'aide d'une clé secrète et joint les informations utilisateur décodées (ID et rôle) à l'objet de demande. Si le jeton est manquant ou non valide, il renvoie une réponse d'erreur appropriée.
- Créer des permis.ts et collez le code suivant:
<span>Permis d'importation à partir de '../utils/permit'; </span> <span>Exporter const checkUsersertOrtSudents = async (e-mail: chaîne, action: chaîne, ressource: chaîne): promesse <boolean> => { </boolean></span><span>essayer { </span><span>const perted = attendre permis.Check (e-mail, action, ressource); </span><span>console.log ("autorisé", autorisé); </span><span>retour autorisé; </span><span>} catch (erreur) { </span><span>Console.Error ( <span>`Error Synccing User <span>$ {e-mail}</span> à permettre.io:`</span> , erreur); </span><span>retourne false; </span><span>} </span><span>}; </span> <span>Exporter const checkUserserpermitSignment = async (e-mail: chaîne, action: chaîne, ressource: chaîne): promesse <boolean> => { </boolean></span><span>essayer { </span><span>const perted = attendre permis.Check (e-mail, action, ressource); </span><span>console.log ("autorisé", autorisé); </span><span>retour autorisé; </span><span>} catch (erreur) { </span><span>Console.Error ( <span>`Error Synccing User <span>$ {e-mail}</span> à permettre.io:`</span> , erreur); </span><span>retourne false; </span><span>} </span><span>};</span>
Ce fichier définit les fonctions utilitaires, vérifiez les objets d'opérates et vérifiez la réserve d'OperperTossign, pour vérifier les autorisations des utilisateurs dans le permis pour des actions et des ressources spécifiques. Les deux fonctions gèrent gracieusement les erreurs, les problèmes de journalisation et le rendement faux si la vérification de l'autorisation échoue. Ils sont utilisés pour appliquer l'autorisation dans l'application.
- Dossier de contrôleurs
- Créer un fichier auth.ts et coller le code suivant:
<span>import {compte, id} de '../config/appwrite'; </span><span>import {request, réponse} de 'express'; </span><span>importer JWT de «jsonwebtoken»; </span> <span>const jwt_secret = process.env.jwt_secret as String; // Assurez-vous que cela est défini dans votre fichier .env </span> <span>// Contrôleur d'inscription </span><span>Export const Signup = async (req: request, res: réponse) => { </span><span>const {email, mot de passe, nom} = req.body; </span> <span>if (! Email ||! Mot de passe ||! Nom) { </span><span>return res.status (400) .json ({error: 'nom, e-mail et mot de passe sont requis.'}); </span><span>} </span> <span>essayer { </span><span>const user = attend compte.Create (id.Unique (), e-mail, mot de passe, nom); </span><span>// Générer JWT </span><span>const token = jwt.sign ({email}, jwt_secret, {expiresin: '8h'}); </span> res <span>.cookie ('token', jeton, { </span> httponly <span>: vrai, </span> Samesite <span>: «strict», </span> Sécurisé <span>: vrai, </span><span>}); </span> res <span>.status (201) .json ({Success: true, utilisateur, token}); </span><span>} catch (erreur: tout) { </span><span>Console.Error ('Erreur d'inscription:', erreur); </span> res <span>.status (500) .json ({succès: false, message: error.sessage}); </span><span>} </span><span>}; </span> <span>// Contrôleur de connexion </span><span>Export const Login = async (req: request, res: réponse) => { </span><span>const {email, mot de passe} = req.body; </span> <span>if (! e-mail ||! mot de passe) { </span><span>return res.status (400) .json ({error: 'e-mail et mot de passe sont requis.'}); </span><span>} </span> <span>essayer { </span><span>const Session = Await Account.CreateeMailPasswordSession (e-mail, mot de passe); </span> <span>// Générer JWT sans rôle </span><span>const token = jwt.sign ( </span><span>{userId: session.userid, e-mail}, // Aucun rôle inclus </span><span>JWT_SECRET, </span><span>{expiresin: '8h'} </span><span>)); </span> res <span>.cookie ('token', jeton, { </span> httponly <span>: vrai, </span> Samesite <span>: «strict», </span> Sécurisé <span>: vrai, </span><span>}); </span> res <span>.status (200) .json ({succès: true, jeton, session}); </span><span>} catch (erreur: tout) { </span><span>Console.Error ('Erreur de connexion:', erreur); </span> res <span>.status (401) .json ({succès: false, message: error.sessage}); </span><span>} </span><span>}; </span> <span>// Contrôleur de déconnexion </span><span>Export const logout = async (req: request, res: réponse) => { </span><span>essayer { </span><span>Await Account.DeleTesession («ID de session actuel»); </span> res <span>.Clearcookie ('token'); </span> res <span>.status (200) .json ({Success: true, message: «déconnecté avec succès»}); </span><span>} catch (erreur: tout) { </span><span>Console.Error ('Erreur de déconnexion:', erreur); </span> res <span>.status (500) .json ({succès: false, message: error.sessage}); </span><span>} </span><span>};</span>
Ce fichier définit les contrôleurs d'authentification pour l'inscription, la connexion et la déconnexion, l'intégration avec AppWrite pour la gestion des utilisateurs et JWT pour la gestion des sessions. Les contrôleurs d'inscription et de connexion valident l'entrée, créent des sessions utilisateur et génèrent des JWT, tandis que le contrôleur de déconnexion efface la session et le jeton. Tous les contrôleurs gèrent les erreurs et renvoient les réponses appropriées.
- Créer un fichier d'affectation.ts et collez le code suivant:
<span>import {request, réponse} de 'express'; </span><span>import {createAsSignmentIndB, AffectmentData, fetchAssignmentsFromdb} de '../Models/assignment'; </span><span>import {checkUsertOperMitAssIgnment} depuis '../middleware/permit'; </span> <span>// Créer une nouvelle affectation </span><span>Exportation de la fonction asynchrone createAsSignment (req: request , res: réponse): promesse <void> { </void></span><span>essayer { </span><span>const {title, sujet, professeur, className, DUeate, CreatoreMail}: affectationData = req.body; </span> <span>const Ispermited = attendre CheckAssertOperMitAssIgnment (CreatoreMail, "Create", "Affectations"); </span><span>if (! Ispermited) { </span> res <span>.status (403) .json ({error: 'non autorisé'}); </span><span>retour; </span><span>} </span> <span>const newAssignment = attendre createassignmentIndB ({ </span> titre <span>, </span> sujet <span>, </span> professeur <span>, </span> ClassName <span>, </span> DÉUDATE <span>, </span> créationmail <span>}); </span> <span>Console.log («Nouvelle affectation créée:», NewAssignment); </span> res <span>.status (201) .json (NewAsignment); </span><span>} catch (erreur) { </span><span>Console.Error ('Erreur Création d'attribution:', Erreur); </span> res <span>.status (500) .json ({erreur: (erreur comme n'importe quel) .Message}); </span><span>} </span><span>} </span> <span>// Récupérer toutes les affectations </span><span>Exportation de la fonction asynchrone FetchAssigments (req: request, res: réponse): promesse <void> { </void></span><span>essayer { </span><span>const {email} = req.params; </span> <span>const Ispermited = attendre CheckAssertOperMitSignment (e-mail, "lire", "affectations"); </span><span>if (! Ispermited) { </span> res <span>.status (403) .json ({message: 'non autorisé'}); </span><span>retour; </span><span>} </span> <span>Affectations constations = attendre fetchAsssignmentsFromdb (); </span> res <span>.status (200) .json (affectations); </span><span>} catch (erreur) { </span> res <span>.status (500) .json ({erreur: (erreur comme n'importe quel) .Message}); </span><span>} </span><span>}</span>
Ce fichier définit les contrôleurs pour la création et la récupération des affectations pour s'intégrer à une base de données et un permis pour les vérifications d'autorisation. Le contrôleur CREATEASSIgnment valide l'entrée, vérifie les autorisations et crée une nouvelle affectation, tandis que le contrôleur FetchAssigments récupère toutes les affectations après avoir vérifié l'accès. Les deux contrôleurs gèrent les erreurs et renvoient les réponses appropriées.
- Créez un fichier student.ts et collez le code suivant:
<span>importer { </span> CreateSTudentindb <span>, </span> fetchstudentsfromdb <span>, </span> Étudiant <span>} de '../Models/student'; </span><span>import {request, réponse} de 'express'; </span><span>import {checkUsertOperMitSudents} depuis '../middleware/permit'; </span> <span>Exportation de la fonction asynchrone CreateStudent (req: request, res: réponse): promesse <void> { </void></span><span>essayer { </span><span>const {FirstName, LastName, Gender, ClassName, Age, CreatoreMail}: StudentData = req.Body; </span> <span>if (! ['girl', 'boy']. Comprend (genre)) { </span> res <span>.status (400) .json ({error: 'invalid Gender Type'}); </span><span>retour; </span><span>} </span> <span>const Ispermited = attendre CheckAssertOperMitStudants (CreatoreMail, "Create", "Students"); </span><span>if (! Ispermited) { </span> res <span>.status (403) .json ({message: 'non autorisé'}); </span><span>retour; </span><span>} </span> <span>const newstudent = attendre createStudentindb ({{ </span> prénom <span>, </span> nom de famille <span>, </span> genre <span>, </span> ClassName <span>, </span> âge <span>, </span> créationmail <span>}); </span> res <span>.status (201) .json (newstudent); </span><span>} catch (erreur) { </span> res <span>.status (500) .json ({erreur: (erreur comme n'importe quel) .Message}); </span><span>} </span><span>} </span> <span>// chercher tous les étudiants </span><span>Exportation de la fonction asynchrone FetchStudents (req: request, res: réponse): promesse <void> { </void></span><span>essayer { </span><span>const {email} = req.params; </span> <span>const Ispermited = attendre CheckUsertOperMitSudents (e-mail, "lire", "étudiants"); </span><span>if (! Ispermited) { </span> res <span>.status (403) .json ({message: 'non autorisé'}); </span><span>retour; </span><span>} </span> <span>Const étudiants = attendre fetchStudentsFromdb (); </span> res <span>.status (200) .json (étudiants); </span><span>} catch (erreur) { </span> res <span>.status (500) .json ({erreur: (erreur comme n'importe quel) .Message}); </span><span>} </span><span>}</span>
Ce fichier définit les contrôleurs pour la création et la récupération des étudiants, l'intégration avec une base de données et le permis pour les vérifications d'autorisation. Le contrôleur CreateSTudent valide l'entrée, vérifie les autorisations et crée un nouvel élève, tandis que le contrôleur FetchStudents récupère tous les étudiants après avoir vérifié l'accès. Les deux contrôleurs gèrent les erreurs et renvoient les réponses appropriées.
- Créez un fichier Profil.ts et collez le code suivant:
<span>import {profil} de '@ / modèles / profil'; </span><span>importer axios à partir de «Axios»; </span><span>import {database, id, requête} de '../config/appwrite'; </span><span>import {request, réponse, nextFunction, requestHandler} de 'express'; </span><span>import {permis_api_key} de '../config/environment'; </span> <span>const ProficeId = process.env.appwrite_profile_collection_id en tant que chaîne; // s'assurer que c'est dans .env </span><span>const databaseid = process.env.appwrite_database_id en tant que chaîne; // s'assurer que c'est dans .env </span><span>const projectId = process.env.permit_project_id comme chaîne </span><span>constanceId = process.env.permit_env_id comme chaîne </span> <span>const permis_api_url = <span>`https://api.permit.io/v2/facts/ <span>$ {projectId}</span> / <span>$ {EnvironmentId}</span> / Users`</span> ; </span><span>const permis_auth_header = { </span> Autorisation <span>: <span>`Bearer <span>$ {permis_api_key}</span> `</span> , </span><span>"Contenu-type": "application / json", </span><span>}; </span> <span>// Créer un contrôleur de profil </span><span>Export const CreateProfile: requestHandler = async (req: request, res: réponse, suivant: nextFunction): promesse <void> => { </void></span><span>const {FirstName, LastName, Email, Role, UserId} = req.Body; </span><span>console.log (req.body); </span> <span>if (! Email ||! Rôle ||! UserId) { </span> res <span>.status (400) .json ({error: 'FirstName, LastName, Email, Role et UserId sont requis.'}); </span><span>retour; </span><span>} </span> <span>// Valider le rôle </span><span>const les autorisations: Profil [«rôle»] [] = [«admin», «enseignant», «élève»]; </span><span>if (! allowingRoles. y compris (rôle)) { </span> res <span>.status (400) .json ({error: 'Rôle non valide. Rôles autorisés: administrateur, enseignant, élève'}); </span><span>retour; </span><span>} </span> <span>essayer { </span><span>const NewUser = Await Database.CreateDocument ( </span> databaseid <span>, </span> ProfilId <span>, </span><span>Id.Unique (), </span><span>{FirstName, LastName, Email, Rôle, UserId} </span><span>)); </span><span>// Étape 2: synchroniser l'utilisateur à permettre.io </span><span>const permitpayload = { </span> Clé <span>: e-mail, </span> e-mail <span>, </span> First_name <span>: FirstName, </span> Last_name <span>: LastName, </span> role_assignments <span>: [{rôle, locataire: "par défaut"}], </span><span>}; </span> <span>Laissez permisponse; </span><span>essayer { </span><span>const Response = Await axios.post (permis_api_url, permestpayload, {Headers: permis_auth_header}); </span> permutResponse <span>= réponse.data; </span><span>Console.log ("Utilisateur synchronisé sur permis.io:", permitResPonse); </span><span>} catch (permisError) { </span><span>if (axios.isaxioSerror (permisError)) { </span><span>Console.Error ("Impossible de synchroniser l'utilisateur avec permis.io:", permerError.Response? .Data || pergetError.Message); </span><span>} autre { </span><span>Console.Error ("Impossible de synchroniser l'utilisateur avec permis.io:", permisError); </span><span>} </span> permutResponse <span>= {error: "n'a pas réussi à se synchroniser avec permis.io"}; </span><span>} </span> <span>// Étape 3: Renvoyez les deux réponses </span> res <span>.status (201) .json ({{{{ </span> Message <span>: "Profil utilisateur créé avec succès", </span> Utilisateur <span>: NewUser, </span> Permis <span>: permestResponse, </span><span>}); </span><span>retour; </span><span>} catch (erreur: tout) { </span> res <span>.status (500) .json ({succès: false, message: error.sessage}); </span><span>retour; </span><span>} </span><span>}; </span> <span>// récupérer le profil par e-mail </span><span>Export const getProfileByEmail = async (req: request, res: réponse, suivant: nextFunction): promesse <void> => { </void></span><span>const {email} = req.params; </span> <span>if (! e-mail) { </span> res <span>.status (400) .json ({error: 'e-mail est requis.'}); </span><span>retour; </span><span>} </span> <span>essayer { </span><span>const Profice = Await Database.ListDocuments ( </span> databaseid <span>, </span> ProfilId <span>, </span><span>[Query.equal ("e-mail", e-mail)] </span><span>)); </span> <span>if (profil.documents.length === 0) { </span> res <span>.status (404) .json ({error: 'profil introuvable'}); </span><span>retour; </span><span>} </span> res <span>.status (200) .json ({succès: true, profil: profil.documents [0]}); </span><span>} catch (erreur: tout) { </span><span>Console.Error ('Profil de récupération d'erreur:', erreur); </span> res <span>.status (500) .json ({succès: false, message: error.sessage}); </span><span>} </span><span>};</span>
Ce fichier définit les contrôleurs pour la création et la récupération des profils d'utilisateurs, l'intégration avec AppWrite pour les opérations de base de données et le permis de synchronisation des rôles. Le contrôleur CreateProfile valide l'entrée, crée un profil et synchronise l'utilisateur pour le permettre, tandis que le contrôleur GetProfileByEmail récupère un profil par e-mail. Les deux contrôleurs gèrent les erreurs et renvoient les réponses appropriées.
- Dossier de configuration
- Créez un fichier appwrite.ts et collez le code suivant:
<span>Importer {client, compte, bases de données, stockage, ID, autorisation, rôle, requête} de 'appwrite'; </span><span>import {appwrite_endpoint, appwrite_project_id, appwrite_api_key} de './environment'; </span> <span>// initialise le client AppWrite </span><span>const Client = nouveau client () </span><span>.Settendpoint (appwrite_endpoint) // AppWrite Endpoint </span><span>.setProject (appwrite_project_id); // ID du projet AppWrite </span> <span>// Ajouter une clé API si disponible (pour les opérations côté serveur) </span><span>if (appwrite_api_key) { </span><span>(client comme n'importe quel) .config.key = appwrite_api_key; // solution de contournement pour définir la clé de l'API </span><span>} </span> <span>// Initialiser les services AppWrite </span><span>constance const = nouveau compte (client); </span><span>const database = nouvelles bases de données (client); </span><span>const Storage = nouveau stockage (client); </span> <span>// Exporter AppWrite Client and Services </span><span>Export {client, compte, base de données, stockage, ID, autorisation, rôle, requête};</span>
Ce fichier initialise et configure le client AppWrite avec le point de terminaison du projet, l'ID et la clé API en option. Il configure et exporte également les services AppWrite comme le compte, les bases de données et le stockage, ainsi que des constantes d'utilité comme l'ID, l'autorisation, le rôle et la requête.
- Créer un fichier Environment.ts et collez le code suivant:
<span>importer dotenv à partir de «Dotenv»; </span>Dotenv <span>.Config (); // Chargez des variables d'environnement de .env </span> <span>exporter const appwrite_endpoint = process.env.appwrite_endpoint || ''; </span><span>export const permis_api_key = process.env.permit_api_key || ''; </span><span>export const permis_project_id = process.env.permit_project_id || ''; </span><span>Export const permis_env_id = process.env.permit_env_id || ''; </span><span>export const appwrite_project_id = process.env.appwrite_project_id || ''; </span><span>Export const database_id = process.env.appwrite_database_id || ''; </span><span>Export CONS Students_Collection_id = process.env.appwrite_students_collection_id || ''; </span><span>Export CONS ASTRESS_COLLECTION_ID = process.env.appwrite_assignments_collection_id || ''; </span> <span>Export const Profice_Collection_id = process.env.appwrite_profile_collection_id || '';</span>
Ce fichier charge les variables d'environnement à partir d'un fichier .env et les exporte en tant que constantes à utiliser dans l'application, telles que AppWrite et les configurations de permis, les ID de base de données et les ID de collecte. Les valeurs par défaut sont fournies sous forme de repli si les variables d'environnement ne sont pas définies.
- Dossier API
- Créez des étudiants et collez le code suivant:
<span>Importer Express à partir de «Express»; </span><span>import {CreateSudent, fetchStudents} de '../Controllers/student'; </span><span>Importez Authmiddleware à partir de '../middleware/auth'; </span> <span>const Router = Express.Router (); </span> <span>// définir les critères d'évaluation liés aux étudiants </span>Router <span>.Post ('/ étudiants', authmiddleware, createSudent); // Créer un nouvel étudiant </span>Router <span>.get ('/ étudiants /: e-mail', authmiddleware, fetchstudents); // chercher tous les étudiants </span><span>Exporter le routeur par défaut; // Exporter l'instance de routeur</span>
Ce fichier met en place un routeur express avec des points de terminaison pour la gestion des données des étudiants. Il comprend des itinéraires pour créer un nouvel étudiant et aller chercher des étudiants, tous deux protégés par un middleware d'authentification (authmiddleware). Le routeur est ensuite exporté pour une utilisation dans l'application.
- Créer un fichier auth.ts et coller le code suivant:
<span>// src / routes / authroutes.ts </span><span>Importer Express à partir de «Express»; </span><span>Importer {Signup, Login, Logout} depuis '../Controllers/Auth'; </span> <span>const Router = Express.Router (); </span> <span>// Définir les critères d'évaluation liés à l'Auth </span>routeur <span>.post ('/ inscription', (req, res, suivant) => {// Route d'inscription </span><span>inscription (req, res) .then (() => { </span><span>suivant(); </span><span>}). Catch ((err) => { </span><span>suivant (err); </span><span>}); </span><span>}); </span>Routeur <span>.Post ('/ Login', (req, res, suivant) => {// Route de connexion </span><span>Login (req, res) .then (() => { </span><span>suivant(); </span><span>}). Catch ((err) => { </span><span>suivant (err); </span><span>}); </span><span>}); </span>routeur <span>.post ('/ déconnexion', déconnexion); // l'itinéraire de déconnexion </span><span>Exporter le routeur par défaut; // Exporter l'instance de routeur</span>
Ce fichier met en place un routeur express avec des points de terminaison pour les actions liées à l'authentification, y compris l'inscription, la connexion et la déconnexion de l'utilisateur. Les voies d'inscription et de connexion gèrent les opérations asynchrones avec une manipulation d'erreurs, tandis que l'itinéraire de déconnexion est simple. Le routeur est exporté pour une utilisation dans l'application.
- Créer un fichier d'affectation.ts et collez le code suivant:
<span>Importer Express à partir de "Express" </span><span>import {createAsSignment, fetchAssignments} à partir de "../Controllers/assignment" </span><span>Importez Authmiddleware à partir de "../middleware/auth" </span> <span>const Router = Express.Router () </span> Routeur <span>.Post ("/ Create", Authmiddleware, CreateAssignment) </span>Router <span>.get ("/: e-mail", authmiddleware, fetchAssignments) </span><span>Exporter le routeur par défaut</span>
Ce fichier configure un routeur express avec des points de terminaison pour gérer les affectations. Il inclut des itinéraires pour créer une affectation et des affectations de récupération, à la fois protégés par un middleware d'authentification (authentiqueware). Le routeur est exporté pour une utilisation dans l'application.
- Créer un fichier Profil.ts et collez le code suivant:
<span>Importer Express à partir de «Express»; </span><span>import {createProfile, getProfileByEmail} de '../Controllers/profile'; </span><span>Importez Authmiddleware à partir de '../middleware/auth'; </span> <span>const Router = Express.Router (); </span> <span>// route pour créer un profil </span>routeur <span>.post ('/ profil', authmiddleware, createProfile); </span> <span>// route pour obtenir un profil par e-mail </span>router <span>.get ('/ profil /: e-mail', authmiddleware, getProfileByEmail); </span><span>Exporter le routeur par défaut;</span>
Ce fichier met en place un routeur express avec des points de terminaison pour la gestion des profils utilisateur. Il comprend des itinéraires pour créer un profil et récupérer un profil par e-mail, tous deux protégés par un middleware d'authentification (authmiddleware). Le routeur est exporté pour une utilisation dans l'application.
- Créer un fichier index.ts et collez le code suivant:
<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.
Conclusion
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.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Différents moteurs JavaScript ont des effets différents lors de l'analyse et de l'exécution du code JavaScript, car les principes d'implémentation et les stratégies d'optimisation de chaque moteur diffèrent. 1. Analyse lexicale: convertir le code source en unité lexicale. 2. Analyse de la grammaire: générer un arbre de syntaxe abstrait. 3. Optimisation et compilation: générer du code machine via le compilateur JIT. 4. Exécuter: Exécutez le code machine. Le moteur V8 optimise grâce à une compilation instantanée et à une classe cachée, SpiderMonkey utilise un système d'inférence de type, résultant en différentes performances de performances sur le même code.

Les applications de JavaScript dans le monde réel incluent la programmation côté serveur, le développement des applications mobiles et le contrôle de l'Internet des objets: 1. La programmation côté serveur est réalisée via Node.js, adaptée au traitement de demande élevé simultané. 2. Le développement d'applications mobiles est effectué par le reactnatif et prend en charge le déploiement multiplateforme. 3. Utilisé pour le contrôle des périphériques IoT via la bibliothèque Johnny-Five, adapté à l'interaction matérielle.

J'ai construit une application SAAS multi-locataire fonctionnelle (une application EdTech) avec votre outil technologique quotidien et vous pouvez faire de même. Premièrement, qu'est-ce qu'une application SaaS multi-locataire? Les applications saas multi-locataires vous permettent de servir plusieurs clients à partir d'un chant

Cet article démontre l'intégration frontale avec un backend sécurisé par permis, construisant une application fonctionnelle EdTech SaaS en utilisant Next.js. Le frontend récupère les autorisations des utilisateurs pour contrôler la visibilité de l'interface utilisateur et garantit que les demandes d'API adhèrent à la base de rôles

JavaScript est le langage central du développement Web moderne et est largement utilisé pour sa diversité et sa flexibilité. 1) Développement frontal: construire des pages Web dynamiques et des applications à une seule page via les opérations DOM et les cadres modernes (tels que React, Vue.js, Angular). 2) Développement côté serveur: Node.js utilise un modèle d'E / S non bloquant pour gérer une concurrence élevée et des applications en temps réel. 3) Développement des applications mobiles et de bureau: le développement de la plate-forme multiplateuse est réalisé par réact noral et électron pour améliorer l'efficacité du développement.

Les dernières tendances de JavaScript incluent la montée en puissance de TypeScript, la popularité des frameworks et bibliothèques modernes et l'application de WebAssembly. Les prospects futurs couvrent des systèmes de type plus puissants, le développement du JavaScript côté serveur, l'expansion de l'intelligence artificielle et de l'apprentissage automatique, et le potentiel de l'informatique IoT et Edge.

JavaScript est la pierre angulaire du développement Web moderne, et ses principales fonctions incluent la programmation axée sur les événements, la génération de contenu dynamique et la programmation asynchrone. 1) La programmation axée sur les événements permet aux pages Web de changer dynamiquement en fonction des opérations utilisateur. 2) La génération de contenu dynamique permet d'ajuster le contenu de la page en fonction des conditions. 3) La programmation asynchrone garantit que l'interface utilisateur n'est pas bloquée. JavaScript est largement utilisé dans l'interaction Web, les applications à une page et le développement côté serveur, améliorant considérablement la flexibilité de l'expérience utilisateur et du développement multiplateforme.

Python est plus adapté à la science des données et à l'apprentissage automatique, tandis que JavaScript est plus adapté au développement frontal et complet. 1. Python est connu pour sa syntaxe concise et son écosystème de bibliothèque riche, et convient à l'analyse des données et au développement Web. 2. JavaScript est le cœur du développement frontal. Node.js prend en charge la programmation côté serveur et convient au développement complet.


Outils d'IA chauds

Undresser.AI Undress
Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover
Outil d'IA en ligne pour supprimer les vêtements des photos.

Undress AI Tool
Images de déshabillage gratuites

Clothoff.io
Dissolvant de vêtements AI

AI Hentai Generator
Générez AI Hentai gratuitement.

Article chaud

Outils chauds

Version Mac de WebStorm
Outils de développement JavaScript utiles

SublimeText3 version chinoise
Version chinoise, très simple à utiliser

Dreamweaver Mac
Outils de développement Web visuel

mPDF
mPDF est une bibliothèque PHP qui peut générer des fichiers PDF à partir de HTML encodé en UTF-8. L'auteur original, Ian Back, a écrit mPDF pour générer des fichiers PDF « à la volée » depuis son site Web et gérer différentes langues. Il est plus lent et produit des fichiers plus volumineux lors de l'utilisation de polices Unicode que les scripts originaux comme HTML2FPDF, mais prend en charge les styles CSS, etc. et présente de nombreuses améliorations. Prend en charge presque toutes les langues, y compris RTL (arabe et hébreu) et CJK (chinois, japonais et coréen). Prend en charge les éléments imbriqués au niveau du bloc (tels que P, DIV),

Télécharger la version Mac de l'éditeur Atom
L'éditeur open source le plus populaire