Déployer une API GraphQL/MongoDB à l'aide des fonctions Netlify
Pawel Rybczynski
Software Engineer
Objectifs Configuration initiale Installation des dépendances Commençons Tout d'abord, ajoutez le fichier tsconfig.json dans le répertoire principal : Maintenant, créons src/server.ts pour les implémentations des serveurs. Puis ajoutons deux fonctions : l'une pour le serveur local et l'autre pour le lambda. OK, nous n'avons pas de résolveurs ou de définitions de types, nous devons donc en créer. Supposons que, dans un premier temps, nous voulions [...]
Objectifs
Configurer les serveurs locaux et les serveurs lambda.
Connectez les deux à MongoDB.
Mettre en œuvre l'authentification de base.
Déployer Apollo sans serveur GraphQL API avec Netlify.
Maintenant, créons src/server.ts pour les implémentations de serveurs. Ajoutez ensuite deux fonctions : l'une pour le serveur local et l'autre pour le serveur lambda.
// src/server.ts
import { ApolloServer as ApolloServerLambda } from "apollo-server-lambda" ;
import { ApolloServer } from "apollo-server" ;
const createLambdaServer = () =>
nouveau ApolloServerLambda({
typeDefs,
résolveurs,
introspection : true,
terrain de jeu : true,
},
}) ;
const createLocalServer = () =>
nouveau ApolloServer({
typeDefs,
résolveurs,
introspection : true,
terrain de jeu : true,
},
}) ;
export { createLambdaServer, createLocalServer } ;
OK, nous n'avons pas de résolveurs ou de définitions de types, nous devons donc en créer. Supposons que, dans un premier temps, nous voulions créer des utilisateurs et recevoir des informations à leur sujet.
// src/schemas.ts
const { gql } = require("apollo-server-lambda") ;
const userSchema = gql`
type User {
id : ID !
email : Chaîne !
name : Chaîne !
}
type Query {
user(id : ID !): Utilisateur !
}
type Mutation {
createUser(name : String !, email : String !, password : String !): Utilisateur !
}
` ;
Mais nous n'avons pas de données... Corrigeons cela 😉
N'oubliez pas d'importer la définition de type et le résolveur sur le serveur.
// src/server.ts
import { ApolloServer as ApolloServerLambda } from "apollo-server-lambda" ;
import { ApolloServer } from "apollo-server" ;
import { typeDefs } from "./schemas" ;
import { resolvers } from "./resolvers" ;
{...}
Se connecter à MongoDB via mongoose
Il est maintenant temps de créer une connexion avec notre base de données. Dans ce cas particulier, il s'agit de MongoDB. C'est une base de données gratuite et facile à maintenir. Mais avant cela, installons deux autres dépendances :
npm install --save mongoose dotenv
La première étape consiste à créer un modèle d'utilisateur.
// src/model.ts
import mongoose, { Document, Error, Schema } from "mongoose" ;
export type User = Document & {
_id : string,
email : string,
name : chaîne de caractères,
password : chaîne de caractères,
} ;
supprimer mongoose.connection.models["User"] ;
const UserSchema : Schema = new Schema({
email : {
type : String,
requis : true,
unique : true,
},
name : {
type : Chaîne,
required : true,
minLength : 3,
maxLength : 32,
},
password : {
type : String,
required : true,
},
}) ;
export const userModel = mongoose.model ("User", UserSchema) ;
Rendre les mots de passe plus sûrs
La sécurité avant tout ! Sécurisons maintenant nos mots de passe en les hachant.
npm install --save bcrypt @types/bcrypt
Maintenant, implémentez la sécurité du mot de passe dans le callback du pre-middleware. Les fonctions pre-middleware sont exécutées l'une après l'autre, lors de l'appel suivant de chaque middleware. Pour sécuriser les mots de passe, nous utilisons une technique qui génère un sel et un hachage lors d'appels de fonction distincts.
// src/model.ts
import bcrypt from "bcrypt" ;
{...}
const SALT_WORK_FACTOR : nombre = 10 ;
UserSchema.pre("save", function (next) {
const user = this as User ;
if (!this.isModified("password")) return next() ;
bcrypt.genSalt(SALT_WORK_FACTOR, function (err, salt) {
if (err) return next(err) ;
bcrypt.hash(user.password, salt, function (err, hash) {
if (err) return next(err) ;
user.password = hash ;
next() ;
}) ;
}) ;
}) ;
{...}
Ajoutez ensuite la méthode comparePasswords à UserSchema :
// src/model.ts
{...}
UserSchema.methods.comparePasswords = function (
candidatePassword : string,
cb : (err : Error | null, same : boolean | null) => void
) {
const user = this as User ;
bcrypt.compare(candidatePassword, user.password, (err, isMatch) => {
if (err) {
return cb(err, null) ;
}
cb(null, isMatch) ;
}) ;
} ;
{...}
Et bien sûr, modifier le type d'utilisateur.
type comparePasswordFunction = (
candidatePassword : string,
cb : (err : Error, isMatch : boolean) => void
) => void ;
export type User = Document & {
id : chaîne de caractères,
email : string,
name : chaîne de caractères,
password : chaîne de caractères,
comparePasswords : comparePasswordFunction,
} ;
Nous pouvons maintenant établir une connexion entre les serveurs et les bases de données. MONGODB_URI est une variable d'environnement qui contient une chaîne de connexion nécessaire pour créer la connexion. Vous pouvez l'obtenir à partir de votre cluster panel après vous être connecté à votre compte MongoDB atlas. Placez-la à l'intérieur de .env
// .env
MONGODB_URI = ... ;
N'oubliez jamais d'ajouter ce fichier à .gitignore. Super ! Ajoutons maintenant une fonction qui permet de se connecter à la base de données.
Le contexte est un objet partagé par tous les résolveurs. Pour le fournir, il suffit d'ajouter une fonction d'initialisation du contexte au constructeur d'ApolloServer. C'est ce que nous allons faire.
Vous devriez obtenir ces informations à l'intérieur du terminal :
Le serveur ir fonctionne à l'adresse http://localhost:4000/
Si oui, ouvrez le http://localhost:4000/ dans votre navigateur.
Le terrain de jeu GraphQL devrait apparaître. Créons un nouvel utilisateur !
Regardez ce que cela donne dans la base de données.
Cool ! Tout fonctionne bien !
Essayons d'obtenir des informations sur l'utilisateur.
Et ajouter le champ du mot de passe...
Bien ! Nous recevons l'erreur Impossible d'interroger le champ "password" sur le type "User"".. Comme vous pouvez le constater, nous n'avons pas ajouté ce champ dans la définition du type d'utilisateur. Il est là à dessein. Nous ne devrions pas permettre l'interrogation d'un mot de passe ou d'autres données sensibles.
Autre chose... Nous pouvons obtenir les données de l'utilisateur sans aucune authentification... ce n'est pas une bonne solution. Nous devons y remédier.
Mais avant...
Configurer Codegen
Utilisons l'outil GraphQL code pour obtenir un type de base compatible, basé sur notre schéma.
Nous avons également besoin d'un moyen de créer un tel jeton. Le meilleur moyen est d'implémenter une requête de login dans notre résolveur.
// resolvers.ts
import { Resolvers, Token, User } from "./generated/graphql" ;
const userResolver : Resolvers = {
Query : {
user : async (_, { id }, { models : { userModel }, auth }) : Promise => {
if (!auth) throw new AuthenticationError("Vous n'êtes pas authentifié") ;
const user = await userModel.findById({ _id : id }).exec() ;
return user ;
},
login : async (
_,
{ email, password },
{ models : { userModel } }
) : Promise => {
const user = await userModel.findOne({ email }).exec() ;
if (!user) throw new AuthenticationError("Invalid credentials") ;
const matchPasswords = bcrypt.compareSync(password, user.password) ;
if (!matchPasswords) throw new AuthenticationError("Invalid credentials") ;
const token = jwt.sign({ id : user.id }, "riddlemethis", {
expiresIn : 60,
}) ;
return { token } ;
},
},
Mutation : {
createUser : async (
_,
{ email, nom, mot de passe },
{ models : { userModel } }
) : Promise => {
const user = await userModel.create({
email,
nom,
mot de passe,
}) ;
return user ;
},
},
} ;
Vous devez également mettre à jour les définitions des types d'utilisateurs en fonction du type de jeton et de la requête de connexion.
type Token {
token : Chaîne !
}
type Query {
user(id : ID !): Utilisateur !
login(email : String !, password : String !): Token !
}
Essayons maintenant d'obtenir l'utilisateur sans jeton
OK, ça marche ! Essayer de se connecter
Essayons à nouveau d'obtenir l'utilisateur, mais cette fois-ci avec un jeton ajouté aux en-têtes
Cool ! Et si nous nous trompons d'identifiants ?
C'est bien !
Préparer le déploiement
Dernière étape : déployer l'api sans serveur avec Netlify !
Créer un dossier lambda dans le répertoire principal et y placer deux fichiers :
Le premier contient le gestionnaire AWS. Il crée une instance de serveur ApolloServerLambda et puis exposer un gestionnaire en utilisant createHandler de cette instance.