Ανάπτυξη GraphQL/MongoDB API χρησιμοποιώντας Netlify Functions
Pawel Rybczynski
Software Engineer
Στόχοι Αρχική ρύθμιση Εγκατάσταση εξαρτήσεων Ας ξεκινήσουμε Αρχικά, προσθέστε το tsconfig.json στον κύριο κατάλογο: Τώρα, ας δημιουργήσουμε το src/server.ts για τις υλοποιήσεις των διακομιστών. Στη συνέχεια, προσθέστε δύο συναρτήσεις: μία για τον τοπικό διακομιστή και μία για το lambda. Εντάξει, δεν έχουμε resolvers ή ορισμούς τύπων, οπότε πρέπει να δημιουργήσουμε κάποιους. Ας υποθέσουμε ότι, αρχικά, θέλουμε [...]
Στόχοι
Διαμορφώστε τόσο τους τοπικούς όσο και τους διακομιστές lambda.
Συνδέστε και τα δύο στη MongoDB.
Εφαρμόστε βασικό έλεγχο ταυτότητας.
Ανάπτυξη του Apollo χωρίς διακομιστή GraphQL API με την Netlify.
Τώρα, ας δημιουργήσουμε src/server.ts για υλοποιήσεις διακομιστών. Στη συνέχεια, προσθέστε δύο συναρτήσεις: μία για τον τοπικό διακομιστή και μία για το lambda.
Εντάξει, δεν έχουμε resolvers ή ορισμούς τύπων, οπότε πρέπει να δημιουργήσουμε μερικούς. Ας υποθέσουμε ότι, αρχικά, θέλουμε να δημιουργήσουμε χρήστες και να λαμβάνουμε πληροφορίες για αυτούς.
Μην ξεχάσετε να εισαγάγετε τον ορισμό τύπου και τον επιλύτη στον διακομιστή.
// src/server.ts
import { ApolloServer as ApolloServerLambda } from "apollo-server-lambda",
import { ApolloServer } from "apollo-server",
import { typeDefs } from "./schemas",
import { resolvers } from "./resolvers",
{...}
Σύνδεση με τη MongoDB μέσω του mongoose
Τώρα είναι η κατάλληλη στιγμή να δημιουργήσουμε μια σύνδεση με τη βάση δεδομένων μας. Στη συγκεκριμένη περίπτωση, θα είναι η MongoDB. Είναι δωρεάν και εύκολη στη συντήρηση. Αλλά πριν από αυτό, ας εγκαταστήσουμε δύο ακόμα εξαρτήσεις:
npm install --save mongoose dotenv
Το πρώτο βήμα είναι η δημιουργία ενός Μοντέλου χρήστη.
Πρώτα η ασφάλεια! Ας ασφαλίσουμε τώρα τους κωδικούς μας με κατακερματισμό.
npm install --save bcrypt @types/bcrypt
Τώρα, υλοποιήστε την ασφάλεια του κωδικού πρόσβασης μέσα στην επανάκληση προ-μεσολάβησης. Οι συναρτήσεις pre-middleware εκτελούνται η μία μετά την άλλη, όταν κάθε middleware καλεί το επόμενο. Για να κάνουμε τους κωδικούς πρόσβασης ασφαλείς, χρησιμοποιούμε μια τεχνική που παράγει ένα salt και ένα hash σε ξεχωριστές κλήσεις συναρτήσεων.
// 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),
user.password = hash,
next(),
});
});
});
{...}
Στη συνέχεια, προσθέστε τη μέθοδο 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),
});
};
{...}
Τώρα μπορούμε να οργανώσουμε μια σύνδεση μεταξύ διακομιστών και βάσεων δεδομένων. MONGODB_URI είναι μια μεταβλητή περιβάλλοντος που περιέχει μια συμβολοσειρά σύνδεσης που απαιτείται για τη δημιουργία της σύνδεσης. Μπορείτε να τη λάβετε από τον πίνακα του cluster σας αφού συνδεθείτε στο λογαριασμό σας MongoDB atlas. Βάλτε την μέσα στο .env
// .env
MONGODB_URI = ...,
Να θυμάστε πάντα να προσθέτετε αυτό το αρχείο στο .gitignore. Υπέροχα! Τώρα ας προσθέσουμε μια συνάρτηση που επιτρέπει τη σύνδεση με την db.
Το πλαίσιο είναι ένα αντικείμενο το οποίο μοιράζονται όλοι οι επιλύτες. Για να το παρέχουμε, πρέπει απλώς να προσθέσουμε μια συνάρτηση αρχικοποίησης του πλαισίου στον κατασκευαστή του ApolloServer. Ας το κάνουμε.
Θα πρέπει να λάβετε τέτοιες πληροφορίες μέσα στο τερματικό:
Ο διακομιστής ir τρέχει στο http://localhost:4000/
Αν ναι, ανοίξτε το http://localhost:4000/ μέσα στο πρόγραμμα περιήγησής σας.
Θα πρέπει να εμφανιστεί η παιδική χαρά GraphQL. Ας δημιουργήσουμε έναν νέο χρήστη!
Ρίξτε μια ματιά στο πώς φαίνεται στη βάση δεδομένων.
Ωραία! Όλα λειτουργούν μια χαρά!
Ας προσπαθήσουμε να πάρουμε μερικές πληροφορίες χρήστη.
Και προσθέστε το πεδίο κωδικού πρόσβασης...
Ωραία! Λαμβάνουμε σφάλμα Δεν είναι δυνατή η αναζήτηση του πεδίου "password" στον τύπο "User".". Όπως μπορείτε να ελέγξετε ξανά, δεν προσθέσαμε αυτό το πεδίο μέσα στον ορισμό του τύπου χρήστη. Βρίσκεται εκεί επίτηδες. Δεν θα πρέπει να καταστήσουμε δυνατή την αναζήτηση οποιουδήποτε κωδικού πρόσβασης ή άλλων ευαίσθητων δεδομένων.
Ένα άλλο πράγμα... Μπορούμε να πάρουμε τα δεδομένα των χρηστών χωρίς έλεγχο ταυτότητας... δεν είναι μια καλή λύση. Πρέπει να το διορθώσουμε.
Αλλά πριν...
Διαμόρφωση του Codegen
Ας χρησιμοποιήσουμε την GraphQL κωδικός γεννήτρια για να πάρει έναν συμβατό βασικό τύπο, με βάση το σχήμα μας.
Δημιουργία checkAuth συνάρτηση για να επαληθεύσει αν το κουπόνι είναι έγκυρο. Θα προσθέσουμε το αποτέλεσμα στο πλαίσιο, ώστε να μπορούμε να έχουμε πρόσβαση σε αυτό μέσα στους resolvers.
Επίσης, χρειαζόμαστε έναν τρόπο για να δημιουργήσουμε ένα τέτοιο token. Ο καλύτερος τρόπος είναι να υλοποιήσουμε ένα ερώτημα σύνδεσης μέσα στον επιλύτη μας.
// 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("You are not authenticated"),
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 },
},
},
Μετάλλαξη: {
createUser: async (
_,
{ email, name, password },
{ models: { userModel } }
): Promise => {
const user = await userModel.create({
email,
name,
password,
});
return user,
},
},
};
Πρέπει επίσης να ενημερώσετε τους ορισμούς τύπου χρήστη ανά τύπο Token και ερώτημα σύνδεσης.
type Token {
token: Token: Συμβολοσειρά!
}
type Query {
user(id: ID!): User!
login(email: String!, password: String!): Token!
}
Ας προσπαθήσουμε τώρα να πάρουμε τον χρήστη χωρίς token
Εντάξει, δουλεύει μια χαρά! Προσπαθήστε να συνδεθείτε
Ας προσπαθήσουμε ξανά να πάρουμε τον χρήστη, αλλά αυτή τη φορά με προσθήκη token στις επικεφαλίδες
Ωραία! Και τι γίνεται αν ορίσουμε λάθος διαπιστευτήρια;
Ωραία!
Προετοιμαστείτε για την ανάπτυξη
Τελευταίο βήμα: αναπτύξτε το serverless api με το Netlify!
Δημιουργία φακέλου lambda στο main dir και βάλτε δύο αρχεία μέσα:
Το πρώτο περιέχει AWS χειριστής. Δημιουργεί μια περίπτωση διακομιστή ApolloServerLambda και στη συνέχεια, εκθέστε έναν χειριστή χρησιμοποιώντας το createHandler αυτής της περίπτωσης.
Υπάρχει όμως ένα πρόβλημα. Δεν μπορούμε να χρησιμοποιήσουμε /src dir επειδή το Netlify δεν μπορεί να φτάσει έξω από το φάκελο lambda. Επομένως, πρέπει να το ομαδοποιήσουμε.
npm install --save ncp
Είναι ένα πακέτο που επιτρέπει την αντιγραφή καταλόγου.
Τώρα αν τρέξετε npm run bundle, μπορείτε να δείτε, ότι σε νεοδημιουργηθείσα lambda/bundle dir έχουμε όλα τα αρχεία από src/.
Ενημερώστε τη διαδρομή εισαγωγής μέσα στο lambda/graphql.ts
// lambda/graphql.ts
import { APIGatewayProxyEvent, Context } from "aws-lambda",
import { createLambdaServer } from "./bundle/server",
{...}
Μπορείτε τώρα να προσθέσετε το lambda/bundle dir στο .gitignore
Ανάπτυξη με το Netlify
Πρέπει να πούμε στο Netlify ποια είναι η εντολή κατασκευής και πού βρίσκονται οι λειτουργίες μας. Για να το κάνουμε αυτό ας δημιουργήσουμε netlify.toml file: