Einsatz von GraphQL/MongoDB API mit Netlify-Funktionen
Pawel Rybczynski
Software Engineer
Ziele Ersteinrichtung Installieren von Abhängigkeiten Fangen wir an. Fügen Sie zunächst die tsconfig.json zum Hauptverzeichnis hinzu: Nun erstellen wir src/server.ts für die Server-Implementierungen. Fügen Sie dann zwei Funktionen hinzu: eine für den lokalen Server und die zweite für Lambda. OK, wir haben keine Resolver oder Typdefinitionen, also müssen wir welche erstellen. Nehmen wir an, dass wir zunächst [...]
Ziele
Konfigurieren Sie sowohl lokale als auch Lambda-Server.
Verbinden Sie beide mit MongoDB.
Implementieren Sie die Basisauthentifizierung.
Einsatz von serverlosem Apollo GraphQL API mit Netlify.
Erstellen wir nun src/server.ts für Server-Implementierungen. Fügen Sie dann zwei Funktionen hinzu: eine für den lokalen Server und die zweite für Lambda.
OK, wir haben keine Resolver oder Typdefinitionen, also müssen wir welche erstellen. Nehmen wir an, dass wir zunächst Benutzer anlegen und Informationen über sie erhalten wollen.
Aber wir haben keine Daten... Das müssen wir ändern 😉 .
Vergessen Sie nicht, die Typdefinition und den Resolver auf den Server zu importieren.
// src/server.ts
importiere { ApolloServer as ApolloServerLambda } aus "apollo-server-lambda";
importieren { ApolloServer } von "apollo-server";
import { typeDefs } from "./schemas";
importieren { resolvers } aus "./resolvers";
{...}
Verbindung mit MongoDB über mongoose
Jetzt ist es an der Zeit, eine Verbindung mit unserer Datenbank herzustellen. In diesem speziellen Fall wird es MongoDB sein. Sie ist kostenlos und einfach zu pflegen. Aber vorher müssen wir noch zwei weitere Abhängigkeiten installieren:
npm install --save mongoose dotenv
Der erste Schritt besteht darin, ein Benutzermodell zu erstellen.
Sicherheit geht vor! Sichern wir nun unsere Kennwörter, indem wir sie verschlüsseln.
npm install --save bcrypt @types/bcrypt
Implementieren Sie nun die Passwortsicherheit innerhalb des Pre-Middleware-Callbacks. Die Pre-Middleware-Funktionen werden nacheinander ausgeführt, wenn jede Middleware den nächsten Aufruf tätigt. Um die Kennwörter sicher zu machen, verwenden wir eine Technik, die ein Salt und einen Hash bei separaten Funktionsaufrufen erzeugt.
// src/model.ts
import bcrypt from "bcrypt";
{...}
const SALT_WORK_FACTOR: number = 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);
benutzer.passwort = hash;
next();
});
});
});
{...}
Fügen Sie dann die Methode comparePasswords zu UserSchema hinzu:
// 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);
});
};
{...}
Jetzt können wir eine Verbindung zwischen Servern und Datenbanken herstellen. MONGODB_URI ist eine Umgebungsvariable, die einen Verbindungsstring enthält, der für die Erstellung der Verbindung benötigt wird. Sie können sie von Ihrem Cluster-Panel abrufen, nachdem Sie sich bei Ihrem MongoDB-Atlas-Konto angemeldet haben. Setzen Sie sie in .env
// .env
MONGODB_URI = ...;
Denken Sie immer daran, diese Datei zu .gitignore. Großartig! Fügen wir nun eine Funktion hinzu, die eine Verbindung mit der Datenbank ermöglicht.
Der Kontext ist ein Objekt, das von allen Resolvern gemeinsam genutzt wird. Um ihn bereitzustellen, müssen wir nur eine Kontextinitialisierungsfunktion zum ApolloServer-Konstruktor hinzufügen. Tun wir es.
Sie sollten diese Informationen im Terminal erhalten:
Server ir läuft unter http://localhost:4000/
Wenn ja, öffnen Sie die http://localhost:4000/ innerhalb Ihres Browsers.
Der GraphQL-Spielplatz sollte erscheinen. Legen wir einen neuen Benutzer an!
Schauen Sie sich an, wie es in der Datenbank aussieht.
Super! Alles funktioniert bestens!
Versuchen wir, einige Benutzerinformationen zu erhalten.
Und fügen Sie das Passwortfeld hinzu...
Toll! Wir erhalten einen Fehler Abfrage des Feldes "Passwort" beim Typ "Benutzer" nicht möglich.. Wie Sie sehen können, haben wir dieses Feld nicht in die Benutzertypdefinition eingefügt. Es ist absichtlich dort. Es sollte nicht möglich sein, ein Passwort oder andere sensible Daten abzufragen.
Und noch etwas... Wir können Benutzerdaten ohne jegliche Authentifizierung abrufen... das ist keine gute Lösung. Wir müssen das in Ordnung bringen.
Aber vorher...
Codegen konfigurieren
Verwenden wir die GraphQL Code Generator, um einen kompatiblen Basistyp zu erhalten, der auf unserem Schema basiert.
erstellen. checkAuth Funktion, um zu überprüfen, ob das Token gültig ist. Wir fügen das Ergebnis zum Kontext hinzu, damit wir innerhalb der Resolver darauf zugreifen können.
Außerdem brauchen wir eine Möglichkeit, ein solches Token zu erstellen. Der beste Weg ist die Implementierung einer Login-Abfrage innerhalb unseres Resolvers.
// 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("Sie sind nicht authentifiziert");
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("Ungültige Anmeldedaten");
const matchPasswords = bcrypt.compareSync(password, user.password);
if (!matchPasswords) throw new AuthenticationError("Ungültige Anmeldedaten");
const token = jwt.sign({ id: user.id }, "riddlemethis", {
expiresIn: 60,
});
return { token };
},
},
Mutation: {
createUser: async (
_,
{ email, name, password },
{ models: { userModel } }
): Promise => {
const user = await userModel.create({
email,
Name,
Passwort,
});
return user;
},
},
};
Sie müssen auch die Benutzertypdefinitionen nach Token-Typ und Anmeldeabfrage aktualisieren.
Typ Token {
Token: String!
}
type Abfrage {
user(id: ID!): User!
login(email: String!, password: String!): Token!
}
Versuchen wir nun, den Benutzer ohne Token zu bekommen
OK, funktioniert einwandfrei! Versuchen Sie, sich anzumelden
Versuchen wir erneut, den Benutzer abzurufen, aber dieses Mal mit einem Token in den Kopfzeilen
Super! Und was, wenn wir falsche Anmeldedaten eingeben?
Schön!
Einsatz vorbereiten
Letzter Schritt: Bereitstellung der serverlosen API mit Netlify!
Ordner erstellen lambda im Hauptverzeichnis und fügen Sie zwei Dateien ein:
Der erste enthält einen AWS-Handler. Er erstellt eine ApolloServerLambda-Serverinstanz und dann einen Handler mit createHandler dieser Instanz ausstellen.
Aber es gibt ein Problem. Wir können nicht verwenden /src dir, weil Netlify nicht außerhalb des Lambda-Ordners zugreifen kann. Also müssen wir es bündeln.
npm install --save ncp
Es ist ein Paket, mit dem man Verzeichnisse kopieren kann.