window.pipedriveLeadboosterConfig = { bas: 'leadbooster-chat.pipedrive.com', företagId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version: 2, } ;(funktion () { var w = fönster if (w.LeadBooster) { console.warn('LeadBooster finns redan') } annars { w.LeadBooster = { q: [], on: funktion (n, h) { this.q.push({ t: "o", n: n, h: h }) }, trigger: funktion (n) { this.q.push({ t: 't', n: n }) }, } } })() Distribuera GraphQL/MongoDB API med hjälp av Netlify Funktioner - The Codest
Codest
  • Om oss
  • Tjänster
    • Utveckling av programvara
      • Frontend-utveckling
      • Backend-utveckling
    • Staff Augmentation
      • Frontend-utvecklare
      • Backend-utvecklare
      • Dataingenjörer
      • Ingenjörer inom molntjänster
      • QA-ingenjörer
      • Övriga
    • Det rådgivande
      • Revision och rådgivning
  • Industrier
    • Fintech & bankverksamhet
    • E-commerce
    • Adtech
    • Hälsoteknik
    • Tillverkning
    • Logistik
    • Fordon
    • IOT
  • Värde för
    • VD OCH KONCERNCHEF
    • CTO
    • Leveranschef
  • Vårt team
  • Fallstudier
  • Vet hur
    • Blogg
    • Möten
    • Webbinarier
    • Resurser
Karriär Ta kontakt med oss
  • Om oss
  • Tjänster
    • Utveckling av programvara
      • Frontend-utveckling
      • Backend-utveckling
    • Staff Augmentation
      • Frontend-utvecklare
      • Backend-utvecklare
      • Dataingenjörer
      • Ingenjörer inom molntjänster
      • QA-ingenjörer
      • Övriga
    • Det rådgivande
      • Revision och rådgivning
  • Värde för
    • VD OCH KONCERNCHEF
    • CTO
    • Leveranschef
  • Vårt team
  • Fallstudier
  • Vet hur
    • Blogg
    • Möten
    • Webbinarier
    • Resurser
Karriär Ta kontakt med oss
Pil tillbaka GÅ TILLBAKA
2021-05-13
Utveckling av programvara

Distribuera GraphQL/MongoDB API med hjälp av Netlify Functions

Codest

Pawel Rybczynski

Software Engineer

Mål Initial installation Installera beroenden Låt oss börja Först lägger vi till tsconfig.json i huvudkatalogen: Låt oss nu skapa src/server.ts för implementeringar av servrar. Lägg sedan till två funktioner: en för den lokala servern och en för lambda. OK, vi har inga resolvers eller typdefinitioner så vi måste skapa några. Låt oss anta att vi till en början vill [...]

Mål

  1. Konfigurera både lokala servrar och lambdaservrar.
  2. Anslut båda till MongoDB.
  3. Implementera grundläggande autentisering.
  4. Driftsättning av serverlösa Apollo GraphQL API med Netlify.
  5. Använd Typescript.

Initial inställning

npm init -y

Installera beroenden

npm install --save typescript graphql aws-lambda @types/aws-lambda

Låt oss börja

Lägg först till tsconfig.json till huvudkatalogen:

 {
 "compilerOptions": {
 "mål": "es5",
 "modul": "commonjs",
 "allowJs": true,
 "strict": true,
 "esModuleInterop": true,
 "skipLibCheck": true,
 "forceConsistentCasingInFileNames": true
 },
 "include": ["src/*.ts", "src/**/*.ts", "src/**/*.js"],
 "exkludera": ["node_modules"]
 }

Låt oss nu skapa src/server.ts för implementeringar av servrar. Lägg sedan till två funktioner: en för lokal server och en för lambda.

// src/server.ts
importera { ApolloServer as ApolloServerLambda } från "apollo-server-lambda";
importera { ApolloServer } från "apollo-server";


const createLambdaServer = () =>
  ny ApolloServerLambda({
    typDefs,
    resolvers,
    introspektion: true,
    lekplats: true,
    },
  });

const createLocalServer = () =>
  ny ApolloServer({
    typDefs,
    resolvers,
    introspektion: true,
    playground: true,
    },
  });

export { createLambdaServer, createLocalServer };

OK, vi har inga resolvers eller typdefinitioner så vi måste skapa några. Låt oss anta att vi till en början vill skapa användare och få information om dem.

// src/schemas.ts
const { gql } = require("apollo-server-lambda");

const userSchema = gql`
  typ Användare {
    id: ID!
    e-post: Sträng!
    name: Sträng!
  }

  typ Frågeställning {
    user(id: ID!): Användare!
  }

  typ Mutation {
    createUser(namn: String!, e-post: String!, lösenord: String!): Användare!
  }
`;

Om du inte är bekant med detta, Apollo förberedde en mycket trevlig handledning

Låt oss nu skapa en användarresolver med en fråga och en mutation.

// src/resolvers.ts
const userResolver = {
  Förfrågan: {
    user: async (förälder, args, kontext, info) => {
      {...}
    },
  },
  Mutation: {
    createUser: async (förälder, args, kontext, info) => {
      {...}
    },
  },
};

Men vi har inga data ... Låt oss fixa det 😉

Glöm inte att importera typdefinition och resolver till servern.

// src/server.ts
importera { ApolloServer as ApolloServerLambda } från "apollo-server-lambda";
importera { ApolloServer } från "apollo-server";

import { typeDefs } från "./schemas";
import { resolvers } från "./resolvers";

{...}

Anslut med MongoDB via mongoose

Nu är det dags att skapa en anslutning till vår databas. I det här fallet kommer det att vara MongoDB. Den är gratis och lätt att underhålla. Men innan dess, låt oss installera ytterligare två beroenden:

npm installera --spara mongoose dotenv

Det första steget är att skapa en användarmodell.

// src/model.ts
import mongoose, { Document, Error, Schema } från "mongoose";

export typ User = Dokument & {
  _id: sträng,
  e-post: sträng,
  name: sträng,
  lösenord: sträng,
};

radera mongoose.connection.models["User"];

const UserSchema: Schema = new Schema({
  e-post: {
    typ: String,
    required: true,
    unique: true,
  },
  namn: {
    typ: Sträng,
    required: true,
    minLängd: 3,
    maxLängd: 32,
  },
  password: {
    typ: Sträng,
    required: true,
  },
});

export const userModel = mongoose.model  ("User", UserSchema);

Gör lösenorden säkrare

Säkerheten först! Låt oss nu säkra våra lösenord genom att hasha dem.

npm installera --spara bcrypt @types/bcrypt

Nu implementerar du lösenordssäkerheten i återuppringningen av pre-middleware. Pre-middleware-funktioner körs en efter en när varje middleware anropar nästa. För att göra lösenorden säkra använder vi en teknik som genererar ett salt och en hash vid separata funktionsanrop.

// src/modell.ts
importera bcrypt från "bcrypt";
{...}
const SALT_WORK_FACTOR: number = 10;

UserSchema.pre("save", function (next) {
  const user = detta som User;
  if (!this.isModified("password")) return next();

  bcrypt.genSalt(SALT_WORK_FACTOR, function (err, salt) {
    if (err) return next(err);

    bcrypt.hash(användare.lösenord, salt, function (err, hash) {
      if (err) return next(err);
      användarens.lösenord = hash;
      nästa();
    });
  });
});
{...}

Lägg sedan till metoden comparePasswords i UserSchema:

// src/modell.ts
{...}
UserSchema.methods.comparePasswords = funktion (
  candidatePassword: sträng,
  cb: (err: Error | null, same: boolean | null) => void
) {
  const user = detta som User;
  bcrypt.compare(candidatePassword, user.password, (err, isMatch) => {
    if (err) {
      return cb(err, null);
    }
    cb(null, isMatch);
  });
};
{...}

Och naturligtvis, modifiera användartypen.

typ comparePasswordFunction = (
  candidatePassword: sträng,
  cb: (err: Error, isMatch: boolean) => void
) => void;

export typ User = Dokument & {
  _id: sträng,
  email: sträng,
  name: sträng,
  lösenord: sträng,
  comparePasswords: comparePasswordFunction,
};

Nu kan vi ordna en förbindelse mellan servrar och databaser. MONGODB_URI är en miljövariabel som innehåller en anslutningssträng som behövs för att skapa anslutningen. Du kan hämta den från din klusterpanel efter att du har loggat in på ditt MongoDB Atlas-konto. Placera den inuti .env

// .env
MONGODB_URI = ...;

Kom alltid ihåg att lägga till den filen i .gitignore. Jättebra! Låt oss nu lägga till en funktion som gör det möjligt att ansluta till db.

// src/server.ts
importera mongoose, { Connection } från "mongoose";
{...}
låt cachedDb: Connection;

const connectToDatabase = async () => {
  if (cachedDb) return;

  await mongoose.connect(process.env.MONGODB_URI || "", {
    useNewUrlParser: true,
    useUnifiedTopology: true,
    useFindAndModify: false,
    useCreateIndex: true,
  });
  cachedDb = mongoose.connection;
};
{...}

Kontexten är ett objekt som delas mellan alla resolvers. För att tillhandahålla det behöver vi bara lägga till en kontextinitialiseringsfunktion i ApolloServer-konstruktören. Låt oss göra det.

// src/server.ts
importera { userModel } från "./models/user.model";
{...}
const createLambdaServer = asynkron () =>
  ny ApolloServerLambda({
    typDefs,
    resolvers,
    introspektion: true,
    lekplats: true,
    context: async () => {
      await connectToDatabase();

      return {
        modeller: {
          userModel,
        },
      };
    },
  });

const createLocalServer = () =>
  ny ApolloServer({
    typDefs,
    resolvers,
    introspektion: true,
    lekplats: true,
    context: async () => {
      await connectToDatabase();

      return {
        modeller: {
          userModel,
        },
      };
    }
  });

Som du kan se skickar vi också Användarmodell genom sammanhang. Vi kan nu uppdatera resolvern:

// resolvers.ts
const userResolver = {
  Förfrågan: {
    user: async (_, { e-post, namn, lösenord }, { modeller: { userModel } }) => {
      const user = await userModel.findById({ _id: id }).exec();
      returnerar användaren;
    },
  },
  Mutation: {
    createUser: async (_, { id }, { models: { userModel } }) => {
      const user = await userModel.create({ e-post, namn, lösenord });
      returnera användare;
    },
  },
};

Det ser bra ut! Skapa nu en grundläggande serverinstans:

// src/index.ts
importera { createLocalServer } från "./server";
require("dotenv").config();

const port = process.env.PORT || 4000;

const server = createLocalServer();

server.listen(port).then(({ url })) => {
  console.log(`Server ir körs på ${url}`);
});

Starta den lokala servern med Nodemon

Sista saken före körning, lägg till nodemon.json

{
  "klocka": ["src"],
  "ext": ".ts,.js",
  "ignorera": [],
  "exec": "ts-node --transpile-only ./src/index.ts"
}

Lägg till skript i paket.json

"skript": {
    "start": "nodemon"
  },

Och spring!

npm start

Du bör få sådan information inne i terminalen:

Server ir körs på http://localhost:4000/

Om ja, öppna http://localhost:4000/ i din webbläsare.

GraphQL-lekplatsen bör visas. Låt oss skapa en ny användare!

createUser.png

Ta en titt på hur det ser ut i databasen.

databasJohnDoe.png

Coolt! Allt fungerar bra!

Låt oss försöka få lite användarinformation.

användareUtanAuth.png

Och lägg till lösenordsfältet...

användareLösenord.png

Trevligt att träffas! Vi får ett felmeddelande Kan inte fråga fältet "lösenord" på typen "Användare".". Som du kan se har vi inte lagt till det här fältet i definitionen av användartypen. Det finns där med avsikt. Vi bör inte göra det möjligt att fråga efter lösenord eller andra känsliga uppgifter.

En annan sak ... Vi kan få användardata utan någon autentisering ... det är inte en bra lösning. Vi måste fixa det.

Men innan dess...

Konfigurera Codegen

Låt oss använda GraphQL kod generator för att få en kompatibel bastyp, baserad på vårt schema.

npm install --save @graphql-codegen/cli @graphql-codegen/introspection
@graphql-codegen/typescript @graphql-codegen/typescript-resolvers

Skapa codegen.yml

skriva över: true
schema: "http://localhost:4000"
genererar:
  ./src/genererad/graphql.ts:
    plugins:
      - "typescript"
      - "typescript-resolvers"
  ./graphql.schema.json:
    plugins:
      - "introspection"

Lägg till codegen skript till paket.json

"skript": {
    "start": "nodemon",
    "codegen": "graphql-codegen --config ./codegen.yml",
  },

Kör sedan skriptet när den lokala servern körs:

npm kör codegen

Vid framgång kommer du att få ett meddelande:

  √ Analysera konfiguration
  √ Generera utdata

Om du får den informationen bör två filer visas:

  • graphql.schema.json i huvudkatalogen
  • graphql.ts i den nyligen skapade sökvägen src/genererad

Vi är mer intresserade av den andra. Om du öppnar det kommer du att märka en fin struktur av typer.

Nu kan vi förbättra våra resolvers:

// src/resolvers.ts
import { Resolvers, Token, User } från "./generated/graphql";

const userResolver: Resolvers = {
  Förfrågan: {
    user: async (_, { id }, { models: { userModel }, auth }): Promise => {
      const user = await userModel.findById({ _id: id }).exec();
      returnerar användaren;
    },
  },
  Mutation: {
    createUser: asynkron (
      _,
      { e-post, namn, lösenord },
      { modeller: { userModel } }
    ): Promise => {
      const user = await userModel.create({
        e-post,
        namn,
        lösenord,
      });
      returnerar användaren;
    },
  },
};

Autentisering

Låt oss nu ställa in en enkel tokenbaserad autentisering.

npm installera --spara jsonwebtoken @types/jsonwebtoken

Skapa checkAuth funktion för att verifiera om token är giltig. Vi kommer att lägga till resultatet i kontexten så att vi kan komma åt det i resolvern.

// src/server.ts
import { IncomingHttpHeaders } from "http";
import {
  APIGatewayProxyEvent,
  APIGatewayProxyEventHeaders,
  Context,
} from "aws-lambda";
import jwt from "jsonwebtoken";
{...}
const checkAuth = async ({ token } : APIGatewayProxyEventHeaders | IncomingHttpHeaders ) => {
  if (typeof token === "string") {
    try {
      return await jwt.verify(token, "riddlemethis");
    } catch (e) {
      throw new AuthenticationError(`Your session expired. Sign in again.`);
    }
  }
};
{...}
const createLambdaServer = async (
  { headers }: APIGatewayProxyEvent,
  context: Context
) => {
  return new ApolloServerLambda({
    typeDefs,
    resolvers,
    context: async () => {
      await connectToDatabase();

      const auth = await checkAuth(headers);

      return {
        auth,
        models: {
          userModel,
        },
      };
    },
  });
};

function createLocalServer() {
  return new ApolloServer({
    typeDefs,
    resolvers,
    introspection: true,
    playground: true,
    context: async ({ req: { headers } = {} }) => {
      const auth = await checkAuth(headers);
      await connectToDatabase();

      return {
         auth,
         models: {
          userModel,
        },
      };
    },
  });
}

Vi behöver också ett sätt att skapa en sådan token. Det bästa sättet är att implementera en inloggningsfråga i vår resolver.

// resolvers.ts
import { Resolvers, Token, User } från "./generated/graphql";

const userResolver: Resolvers = {
  Förfrågan: {
    user: async (_, { id }, { models: { userModel }, auth }): Promise => {
      if (!auth) throw new AuthenticationError("Du är inte autentiserad");

      const user = await userModel.findById({ _id: id }).exec();
      returnerar användaren;
    },
    login: async (
      _,
      { e-post, lösenord },
      { modeller: { userModel } }
    ): Promise => {
      const user = await userModel.findOne({ email }).exec();

      if (!user) throw new AuthenticationError("Ogiltiga autentiseringsuppgifter");

      const matchPasswords = bcrypt.compareSync(lösenord, användarens.lösenord);

      if (!matchPasswords) throw new AuthenticationError("Ogiltiga referenser");

      const token = jwt.sign({ id: user.id }, "riddlemethis", {
        expiresIn: 60,
      });

      returnera { token };
    },
  },
  Mutation: {
    createUser: asynkron (
      _,
      { e-post, namn, lösenord },
      { modeller: { userModel } }
    ): Promise => {
      const user = await userModel.create({
        e-post,
        namn,
        lösenord,
      });
      returnerar användaren;
    },
  },
};

Du måste också uppdatera definitionerna av användartyper för Token-typ och inloggningsfråga.

typ Token {
    token: String!
  }

typ Frågeställning {
    user(id: ID!): Användare!
    login(email: String!, password: String!): Token!
  }

Låt oss nu försöka hämta användaren utan token

noAuth.png

OK, det fungerar bra! Försök att logga in

token.png

Låt oss försöka igen att hämta användaren, men den här gången med token tillagd i rubrikerna

medToken.png

Coolt! Vad händer om vi anger fel inloggningsuppgifter?

felEmail.png

Snyggt!

Förbered för driftsättning

Sista steget: distribuera serverlöst api med Netlify!

Skapa mapp lambda i huvuddirektören och lägg in två filer inuti:

Första innehåller AWS hanterare. Den skapar en ApolloServerLambda-serverinstans och
och exponera sedan en hanterare med hjälp av createHandler för den instansen.

// lambda/graphql.ts
import { APIGatewayProxyEvent, Context } från "aws-lambda";
importera { createLambdaServer } från "???";

export const handler = async (
  event: APIGatewayProxyEvent,
  context: Kontext
) => {
  const server = await createLambdaServer(event, context);

  return new Promise((res, rej) => {
    const cb = (err: Error, args: any) => (err ? rej(err) : res(args));
    server.createHandler()(event, context, cb);
  });
};

Du kan läsa mer om det.

Den andra är tsconfig. En viktig del är utDir fält.

// lambda/tsconfig.json
{
  "compilerOptions": {
    "sourceMap": true,
    "noImplicitAny": true,
    "module":"commonjs",
    "target": "es6",
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "moduleResolution": "nod",
    "skipLibCheck": true,
    "esModuleInterop": true,
    "outDir": "./dist"
  },
  "include": ["./*.ts", "./**/*.ts", "./**/*.js"]
}

Men det finns ett problem. Vi kan inte använda /source dir eftersom Netlify inte kan nå utanför Lambda-mappen. Så vi måste bunta ihop det.

npm installera --spara ncp

Det är ett paket som gör det möjligt att kopiera katalog.

Lägg till bunt skript till paket.json

"skript": {
    "bundle": "ncp ./src ./lambda/bundle",
    "codegen": "graphql-codegen --config ./codegen.yml",
    "start": "nodemon",
  },

Om du nu kör npm kör buntkan du se, att i nyligen skapade lambda/bundle dir har vi alla filer från src/.

Uppdatera importvägen inuti lambda/graphql.ts

// lambda/graphql.ts
importera { APIGatewayProxyEvent, Context } från "aws-lambda";
importera { createLambdaServer } från "./bundle/server";

{...}

Du kan nu lägga till lambda/bundle dir till .gitignore

Distribuera med Netlify

Vi måste tala om för Netlify vad build-kommandot är och var våra funktioner finns. För att göra det låt oss skapa netlify.toml file:

// netlify.toml
[bygga]
  kommando = "npm run build:lambda"
  funktioner = "lambda/dist"

Som du kan se är det samma katalog som definieras som en utDir fält i lambda/tsconfig.json

Så här ska din appstruktur se ut (ja... en del av den ;))

app
└────lambda
│ └────bundle
│ │ graphql.ts
│ tsconfig.json.ts
└───src
│ └────genererad
│ │ │ graphql.ts
│ │ index.ts
│ │ model.ts
│ │ resolvers.ts
│ │ schemas.ts
│ server.ts
│
│ codegen.yml
│ graphql.schema.json
│ netlify.toml
│ nodemon.json
│ tsconfig.json

Lägg till paket:lambda skript till paket.json

"skript": {
    "build:lambda": "npm run bundle && tsc -p lambda/tsconfig.json",
    "bundle": "ncp ./src ./lambda/bundle",
    "codegen": "graphql-codegen --config ./codegen.yml",
    "start": "nodemon",
  },

Distribuera

Låt oss prova att distribuera vår app via Netlify.

  1. Logga in på ditt Netlify-konto,
  2. Anslut till din Github,
  3. Klicka på knappen "Ny webbplats från Git",
  4. Välj ett korrekt repo,
  5. Ställ in byggkommandot npm run build:lambda,
  6. Lägg till miljövariabel (MONGODB_URI),
  7. Distribuera...

Och TAAADAAAAAA...

pageNotFound.png

Det beror på att slutpunkten som standard inte är http://page/ men http://page/.netlify/functions/graphql.

utanRedirect.png

Hur fixar jag det? Det är mycket enkelt. Skapa bara _Omdirigeringar med:

/ /.netlify/functions/graphql 200!

Distribuera igen och kontrollera.

omdirigera.png

Hoppas du gillade det! Du får gärna förbättra och ändra.

Läs mer om detta:

Hur undviker man att döda ett projekt med dåliga kodningsrutiner?

Säkerhet i webbapplikationer. Sårbarhet med mål="_blank"

Säkerhet i webbapplikationer - XSS-sårbarhet

Relaterade artiklar

Utveckling av programvara

Bygg framtidssäkrade webbappar: Insikter från The Codest:s expertteam

Upptäck hur The Codest utmärker sig genom att skapa skalbara, interaktiva webbapplikationer med banbrytande teknik som ger sömlösa användarupplevelser på alla plattformar. Läs om hur vår expertis driver digital omvandling och affärsutveckling...

DEKODEST
Utveckling av programvara

Topp 10 Lettlandsbaserade mjukvaruutvecklingsföretag

Läs mer om Lettlands främsta mjukvaruutvecklingsföretag och deras innovativa lösningar i vår senaste artikel. Upptäck hur dessa teknikledare kan hjälpa till att lyfta ditt företag.

thecodest
Lösningar för företag och uppskalningsföretag

Java Software Development Essentials: En guide till framgångsrik outsourcing

Utforska denna viktiga guide om framgångsrik outsourcing av Java-programvaruutveckling för att förbättra effektiviteten, få tillgång till expertis och driva projektframgång med The Codest.

thecodest
Utveckling av programvara

Den ultimata guiden till outsourcing i Polen

Den kraftiga ökningen av outsourcing i Polen drivs av ekonomiska, utbildningsmässiga och tekniska framsteg, vilket främjar IT-tillväxt och ett företagsvänligt klimat.

TheCodest
Lösningar för företag och uppskalningsföretag

Den kompletta guiden till verktyg och tekniker för IT-revision

IT-revisioner säkerställer säkra, effektiva och kompatibla system. Läs mer om hur viktiga de är genom att läsa hela artikeln.

Codest
Jakub Jakubowicz CTO och medgrundare

Prenumerera på vår kunskapsbas och håll dig uppdaterad om expertisen från IT-sektorn.

    Om oss

    The Codest - Internationellt mjukvaruutvecklingsföretag med teknikhubbar i Polen.

    Förenade kungariket - Huvudkontor

    • Kontor 303B, 182-184 High Street North E6 2JA
      London, England

    Polen - Lokala tekniknav

    • Fabryczna Office Park, Aleja
      Pokoju 18, 31-564 Kraków
    • Brain Embassy, Konstruktorska
      11, 02-673 Warszawa, Polen

      Codest

    • Hem
    • Om oss
    • Tjänster
    • Fallstudier
    • Vet hur
    • Karriär
    • Ordbok

      Tjänster

    • Det rådgivande
    • Utveckling av programvara
    • Backend-utveckling
    • Frontend-utveckling
    • Staff Augmentation
    • Backend-utvecklare
    • Ingenjörer inom molntjänster
    • Dataingenjörer
    • Övriga
    • QA-ingenjörer

      Resurser

    • Fakta och myter om att samarbeta med en extern partner för mjukvaruutveckling
    • Från USA till Europa: Varför väljer amerikanska startup-företag att flytta till Europa?
    • Jämförelse av Tech Offshore Development Hubs: Tech Offshore Europa (Polen), ASEAN (Filippinerna), Eurasien (Turkiet)
    • Vilka är de största utmaningarna för CTO:er och CIO:er?
    • Codest
    • Codest
    • Codest
    • Privacy policy
    • Användarvillkor för webbplatsen

    Copyright © 2025 av The Codest. Alla rättigheter reserverade.

    sv_SESwedish
    en_USEnglish de_DEGerman da_DKDanish nb_NONorwegian fiFinnish fr_FRFrench pl_PLPolish arArabic it_ITItalian jaJapanese ko_KRKorean es_ESSpanish nl_NLDutch etEstonian elGreek sv_SESwedish