window.pipedriveLeadboosterConfig = { base: 'leadbooster-chat.pipedrive.com', companyId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version: 2, } ;(funktion () { var w = vindue if (w.LeadBooster) { console.warn('LeadBooster findes allerede') } else { w.LeadBooster = { q: [], on: function (n, h) { this.q.push({ t: 'o', n: n, h: h }) }, trigger: function (n) { this.q.push({ t: 't', n: n }) }, } } })() Implementer GraphQL/MongoDB API ved hjælp af Netlify-funktioner - The Codest
Codest
  • Om os
  • Serviceydelser
    • Udvikling af software
      • Frontend-udvikling
      • Backend-udvikling
    • Staff Augmentation
      • Frontend-udviklere
      • Backend-udviklere
      • Dataingeniører
      • Cloud-ingeniører
      • QA-ingeniører
      • Andet
    • Det rådgivende
      • Revision og rådgivning
  • Industrier
    • Fintech og bankvirksomhed
    • E-commerce
    • Adtech
    • Sundhedsteknologi
    • Produktion
    • Logistik
    • Biler
    • IOT
  • Værdi for
    • ADMINISTRERENDE DIREKTØR
    • CTO
    • Leder af levering
  • Vores team
  • Casestudier
  • Ved hvordan
    • Blog
    • Møder
    • Webinarer
    • Ressourcer
Karriere Tag kontakt til os
  • Om os
  • Serviceydelser
    • Udvikling af software
      • Frontend-udvikling
      • Backend-udvikling
    • Staff Augmentation
      • Frontend-udviklere
      • Backend-udviklere
      • Dataingeniører
      • Cloud-ingeniører
      • QA-ingeniører
      • Andet
    • Det rådgivende
      • Revision og rådgivning
  • Værdi for
    • ADMINISTRERENDE DIREKTØR
    • CTO
    • Leder af levering
  • Vores team
  • Casestudier
  • Ved hvordan
    • Blog
    • Møder
    • Webinarer
    • Ressourcer
Karriere Tag kontakt til os
Pil tilbage GÅ TILBAGE
2021-05-13
Udvikling af software

Implementer GraphQL/MongoDB API ved hjælp af Netlify-funktioner

Codest

Pawel Rybczynski

Software Engineer

Mål Indledende opsætning Installer afhængigheder Lad os starte Først skal du tilføje tsconfig.json til hovedmappen: Lad os nu oprette src/server.ts til serverimplementeringer. Tilføj derefter to funktioner: en til den lokale server og en anden til lambda. OK, vi har ingen resolvere eller typedefinitioner, så vi er nødt til at oprette nogle. Lad os antage, at vi i første omgang ønsker [...]

Mål

  1. Konfigurer både lokale og lambda-servere.
  2. Forbind begge til MongoDB.
  3. Implementer grundlæggende godkendelse.
  4. Implementer serverløs Apollo GraphQL API med Netlify.
  5. Brug Typescript.

Første opsætning

npm init -y

Installer afhængigheder

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

Lad os begynde

Først skal du tilføje tsconfig.json til hovedmappen:

 {
 "compilerOptions": {
 "target": "es5",
 "module": "commonjs",
 "allowJs": true,
 "strict": true,
 "esModuleInterop": true,
 "skipLibCheck": true,
 "forceConsistentCasingInFileNames": true
 },
 "include": ["src/*.ts", "src/**/*.ts", "src/**/*.js"],
 "exclude": ["node_modules"]
 }

Lad os nu oprette src/server.ts til implementering af servere. Tilføj derefter to funktioner: en til den lokale server og en anden til lambda.

// src/server.ts
import { ApolloServer as ApolloServerLambda } from "apollo-server-lambda";
import { ApolloServer } fra "apollo-server";


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

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

export { createLambdaServer, createLocalServer };

OK, vi har ingen resolvere eller typedefinitioner, så vi er nødt til at skabe nogle. Lad os antage, at vi i første omgang ønsker at oprette brugere og modtage oplysninger om dem.

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

const userSchema = gql`
  type Bruger {
    id: ID!
    e-mail: String!
    navn: String!
  }

  type Forespørgsel {
    user(id: ID!): Bruger!
  }

  type Mutation {
    createUser(name: String!, email: String!, password: String!): User!
  }
`;

Hvis du ikke er bekendt med det, Apollo har lavet en meget flot vejledning

Lad os nu oprette en brugerresolver med en forespørgsel og en mutation.

// src/resolvers.ts
const userResolver = {
  Forespørgsel: {
    user: async (parent, args, context, info) => {
      {...}
    },
  },
  Mutation: {
    createUser: async (parent, args, context, info) => {
      {...}
    },
  },
};

Men vi har ingen data ... Lad os ordne det 😉.

Glem ikke at importere typedefinition og resolver til serveren.

// src/server.ts
import { ApolloServer as ApolloServerLambda } from "apollo-server-lambda";
import { ApolloServer } fra "apollo-server";

import { typeDefs } fra "./schemas";
import { resolvers } fra "./resolvers";

{...}

Opret forbindelse til MongoDB via mongoose

Nu er det et godt tidspunkt at oprette en forbindelse til vores database. I dette tilfælde vil det være MongoDB. Den er gratis og nem at vedligeholde. Men inden da skal vi installere yderligere to afhængigheder:

npm install --save mongoose dotenv

Det første skridt er at oprette en brugermodel.

// src/model.ts
import mongoose, { Document, Error, Schema } from "mongoose";

export type Bruger = Dokument & {
  _id: string,
  e-mail: string,
  navn: string,
  password: string,
};

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

const UserSchema: Schema = new Schema({
  e-mail: {
    type: String,
    required: true,
    unique: true,
  },
  navn: {
    type: String,
    required: true,
    minLængde: 3,
    maxLength: 32,
  },
  adgangskode: {
    type: String,
    required: true,
  },
});

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

Gør passwords mere sikre

Sikkerhed først! Lad os nu sikre vores adgangskoder ved at hashe dem.

npm install --save bcrypt @types/bcrypt

Nu skal du implementere password-sikkerheden i pre-middleware-kaldet. Pre-middleware-funktioner udføres en efter en, når hver middleware kalder næste gang. For at gøre adgangskoderne sikre bruger vi en teknik, der genererer et salt og en hash ved separate funktionskald.

// src/model.ts
import bcrypt fra "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);
      bruger.adgangskode = hash;
      næste();
    });
  });
});
{...}

Tilføj derefter metoden comparePasswords til 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) => {
    hvis (err) {
      return cb(err, null);
    }
    cb(null, isMatch);
  });
};
{...}

Og selvfølgelig skal du ændre brugertypen.

type comparePasswordFunction = (
  candidatePassword: string,
  cb: (err: Error, isMatch: boolean) => void
) => void;

export type User = Dokument & {
  _id: string,
  e-mail: string,
  navn: string,
  password: string,
  comparePasswords: comparePasswordFunction,
};

Nu kan vi etablere en forbindelse mellem servere og databaser. MONGODB_URI er en miljøvariabel, som indeholder en forbindelsesstreng, der er nødvendig for at oprette forbindelsen. Du kan få den fra dit klyngepanel, når du har logget ind på din MongoDB atlas-konto. Sæt den ind i .env

// .env
MONGODB_URI = ...;

Husk altid at tilføje filen til .gitignore. Super! Lad os nu tilføje en funktion, der gør det muligt at oprette forbindelse til db.

// src/server.ts
import mongoose, { Connection } from "mongoose";
{...}
lad 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;
};
{...}

Konteksten er et objekt, som deles på tværs af alle resolvere. For at tilvejebringe det skal vi bare tilføje en kontekstinitialiseringsfunktion til ApolloServer-konstruktøren. Lad os gøre det.

// src/server.ts
import { userModel } fra "./models/user.model";
{...}
const createLambdaServer = async () =>
  ny ApolloServerLambda({
    typeDefs,
    resolvers,
    introspektion: true,
    legeplads: true,
    context: async () => {
      await connectToDatabase();

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

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

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

Som du kan se, sender vi også userModel igennem sammenhæng. Vi kan nu opdatere resolveren:

// resolvers.ts
const userResolver = {
  Forespørgsel: {
    user: async (_, { email, name, password }, { models: { userModel } }) => {
      const user = await userModel.findById({ _id: id }).exec();
      returnerer bruger;
    },
  },
  Mutation: {
    createUser: async (_, { id }, { models: { userModel } }) => {
      const user = await userModel.create({ email, name, password });
      returnerer bruger;
    },
  },
};

Det ser godt ud! Opret nu en grundlæggende serverinstans:

// src/index.ts
import { createLocalServer } fra "./server";
require("dotenv").config();

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

const server = createLocalServer();

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

Start den lokale server med Nodemon

Sidste ting før kørsel, tilføj nodemon.json

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

Tilføj script til pakke.json

"scripts": {
    "start": "nodemon"
  },

Og løb!

npm start

Du bør få sådanne oplysninger inde i terminalen:

Serveren kører på http://localhost:4000/

Hvis ja, skal du åbne http://localhost:4000/ inde i din browser.

GraphQL-legepladsen burde dukke op. Lad os oprette en ny bruger!

createUser.png

Se, hvordan det ser ud i databasen.

databaseJohnDoe.png

Fedt! Alt fungerer fint!

Lad os prøve at få nogle brugeroplysninger.

userWithoutAuth.png

Og tilføj adgangskodefeltet...

userPassword.png

Flot! Vi modtager en fejl Kan ikke forespørge på feltet "password" på typen "User".". Som du kan se, har vi ikke tilføjet dette felt i brugertypedefinitionen. Det er der med vilje. Vi bør ikke gøre det muligt at forespørge på en adgangskode eller andre følsomme data.

En anden ting ... Vi kan få brugerdata uden nogen form for godkendelse ... det er ikke en god løsning. Vi er nødt til at fikse det.

Men inden...

Konfigurer Codegen

Lad os bruge GraphQL Kode generator for at få en kompatibel basistype baseret på vores skema.

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

Opret codegen.yml

overskrivning: true
schema: "http://localhost:4000"
genererer:
  ./src/generated/graphql.ts:
    plugins:
      - "typescript"
      - "typescript-resolvers"
  ./graphql.schema.json:
    plugins:
      - "introspection"

Tilføj Codegen script til pakke.json

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

Kør derefter scriptet, når den lokale server kører:

npm kør codegen

Hvis det lykkes, får du en besked:

  √ Parse konfiguration
  √ Generer output

Hvis du modtager den info, skulle der gerne dukke to filer op:

  • graphql.schema.json i hovedmappen
  • graphql.ts i den nyoprettede sti src/genereret

Vi er mere interesserede i den anden. Hvis du åbner den, vil du se en fin struktur af typer.

Nu kan vi forbedre vores opløsere:

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

const userResolver: Resolvers = {
  Forespørgsel: {
    user: async (_, { id }, { models: { userModel }, auth }): Promise => {
      const user = await userModel.findById({ _id: id }).exec();
      return user;
    },
  },
  Mutation: {
    createUser: async (
      _,
      { e-mail, navn, adgangskode },
      { models: { userModel } }
    ): Promise => {
      const user = await userModel.create({
        email,
        name,
        password,
      });
      returnerer bruger;
    },
  },
};

Autentificering

Lad os nu sætte en simpel token-baseret autentificering op.

npm install --save jsonwebtoken @types/jsonwebtoken

Opret checkAuth funktion til at verificere, om tokenet er gyldigt. Vi tilføjer resultatet til konteksten, så vi kan få adgang til det i resolverne.

// 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 har også brug for en måde at oprette et sådant token på. Den bedste måde er at implementere en login-forespørgsel i vores resolver.

// resolvers.ts
import { Resolvers, Token, User } fra "./generated/graphql";

const userResolver: Resolvers = {
  Forespørgsel: {
    user: async (_, { id }, { models: { userModel }, auth }): Promise => {
      if (!auth) throw new AuthenticationError("Du er ikke autentificeret");

      const user = await userModel.findById({ _id: id }).exec();
      returnerer bruger;
    },
    login: async (
      _,
      { email, password },
      { models: { userModel } }
    ): Promise => {
      const user = await userModel.findOne({ email }).exec();

      if (!user) throw new AuthenticationError("Ugyldige legitimationsoplysninger");

      const matchPasswords = bcrypt.compareSync(password, user.password);

      if (!matchPasswords) throw new AuthenticationError("Ugyldige legitimationsoplysninger");

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

      return { token };
    },
  },
  Mutation: {
    createUser: async (
      _,
      { e-mail, navn, adgangskode },
      { models: { userModel } }
    ): Promise => {
      const user = await userModel.create({
        email,
        name,
        password,
      });
      returnerer bruger;
    },
  },
};

Du skal også opdatere brugertypedefinitioner efter Token-type og login-forespørgsel.

type Token {
    token: String!
  }

type Forespørgsel {
    user(id: ID!): Bruger!
    login(email: String!, password: String!): Token!
  }

Lad os nu prøve at hente brugeren uden token

noAuth.png

OK, det virker fint! Prøv at logge ind

token.png

Lad os prøve igen at hente brugeren, men denne gang med token tilføjet til overskrifterne

medToken.png

Fedt nok! Og hvad hvis vi indstiller forkerte legitimationsoplysninger?

forkertEmail.png

Flot!

Gør klar til udrulning

Sidste trin: Implementer serverløs api med Netlify!

Opret mappe lambda i main dir, og læg to filer ind:

Den første indeholder AWS handler. Den opretter en ApolloServerLambda-serverinstans og
og derefter eksponere en handler ved hjælp af createHandler for denne instans.

// lambda/graphql.ts
import { APIGatewayProxyEvent, Context } from "aws-lambda";
import { createLambdaServer } from "???";

export const handler = async (
  event: APIGatewayProxyEvent,
  context: Kontekst
) => {
  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æse mere om det.

Den anden er tsconfig. Den vigtige del er outDir felt.

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

Men der er et problem. Vi kan ikke bruge /src dir, fordi Netlify ikke kan nå uden for lambda-mappen. Så vi må bundle det.

npm install --save ncp

Det er en pakke, der gør det muligt at kopiere mapper.

Tilføj bundt script til pakke.json

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

Hvis du nu kører npm kør bundlekan du se, at i nyoprettede lambda/bundle dir har vi alle filer fra src/.

Opdater importstien inde i lambda/graphql.ts

// lambda/graphql.ts
import { APIGatewayProxyEvent, Context } from "aws-lambda";
import { createLambdaServer } fra "./bundle/server";

{...}

Du kan nu tilføje lambda/bundle dir til .gitignore

Udrulning med Netlify

Vi skal fortælle Netlify, hvad build-kommandoen er, og hvor vores funktioner bor. For at gøre det, lad os oprette netlify.toml file:

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

Som du kan se, er det den samme mappe, som er defineret som en outDir felt i lambda/tsconfig.json

Sådan skal din app-struktur se ud (eller... en del af den ;))

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

Tilføj bundle:lambda script til pakke.json

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

Udrulning

Lad os prøve at distribuere vores app via Netlify.

  1. Log ind på din Netlify-konto,
  2. Opret forbindelse til din Github,
  3. Klik på knappen "Nyt websted fra Git",
  4. Vælg et passende repo,
  5. Indstil build-kommandoen npm run build:lambda,
  6. Tilføj miljøvariabel (MONGODB_URI),
  7. Udrulning...

Og TAAADAAAA...

pageNotFound.png

Det skyldes, at slutpunktet som standard ikke er http://page/ men http://page/.netlify/functions/graphql.

withoutRedirect.png

Hvordan fikser man det? Det er meget enkelt. Du skal bare oprette _omdirigeringer med:

/.netlify/functions/graphql 200!

Implementer igen og tjek.

omdirigering.png

Jeg håber, du kunne lide det! Du er velkommen til at forbedre og ændre.

Læs mere om det:

Hvordan dræber man ikke et projekt med dårlig kodningspraksis?

Sikkerhed i webapps. Target="_blank" sårbarhed

Webapp-sikkerhed - XSS-sårbarhed

Relaterede artikler

Udvikling af software

Byg fremtidssikrede webapps: Indsigt fra The Codest's ekspertteam

Oplev, hvordan The Codest udmærker sig ved at skabe skalerbare, interaktive webapplikationer med banebrydende teknologier, der leverer sømløse brugeroplevelser på tværs af alle platforme. Lær, hvordan vores ekspertise driver digital transformation og...

DENKODEST
Udvikling af software

Top 10 Letlands-baserede softwareudviklingsvirksomheder

Læs om Letlands bedste softwareudviklingsvirksomheder og deres innovative løsninger i vores seneste artikel. Find ud af, hvordan disse teknologiledere kan hjælpe med at løfte din virksomhed.

thecodest
Løsninger til virksomheder og scaleups

Grundlæggende om Java-softwareudvikling: En guide til succesfuld outsourcing

Udforsk denne vigtige guide til vellykket outsourcing af Java-softwareudvikling for at forbedre effektiviteten, få adgang til ekspertise og skabe projektsucces med The Codest.

thecodest
Udvikling af software

Den ultimative guide til outsourcing i Polen

Den voldsomme stigning i outsourcing i Polen er drevet af økonomiske, uddannelsesmæssige og teknologiske fremskridt, der fremmer it-vækst og et erhvervsvenligt klima.

TheCodest
Løsninger til virksomheder og scaleups

Den komplette guide til IT-revisionsværktøjer og -teknikker

IT-revisioner sikrer sikre, effektive og kompatible systemer. Lær mere om deres betydning ved at læse hele artiklen.

Codest
Jakub Jakubowicz CTO og medstifter

Tilmeld dig vores vidensbase, og hold dig opdateret om ekspertisen fra it-sektoren.

    Om os

    The Codest - International softwareudviklingsvirksomhed med tech-hubs i Polen.

    Storbritannien - Hovedkvarter

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

    Polen - Lokale teknologiske knudepunkter

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

      Codest

    • Hjem
    • Om os
    • Serviceydelser
    • Casestudier
    • Ved hvordan
    • Karriere
    • Ordbog

      Serviceydelser

    • Det rådgivende
    • Udvikling af software
    • Backend-udvikling
    • Frontend-udvikling
    • Staff Augmentation
    • Backend-udviklere
    • Cloud-ingeniører
    • Dataingeniører
    • Andet
    • QA-ingeniører

      Ressourcer

    • Fakta og myter om at samarbejde med en ekstern softwareudviklingspartner
    • Fra USA til Europa: Hvorfor beslutter amerikanske startups sig for at flytte til Europa?
    • Sammenligning af Tech Offshore-udviklingsknudepunkter: Tech Offshore Europa (Polen), ASEAN (Filippinerne), Eurasien (Tyrkiet)
    • Hvad er de største udfordringer for CTO'er og CIO'er?
    • Codest
    • Codest
    • Codest
    • Privacy policy
    • Vilkår for brug af hjemmesiden

    Copyright © 2025 af The Codest. Alle rettigheder forbeholdes.

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