window.pipedriveLeadboosterConfig = { base: 'leadbooster-chat.pipedrive.com', companyId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', versjon: 2, } ;(function () { var w = vindu if (w.LeadBooster) { console.warn('LeadBooster finnes 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 }) }, } } })() Distribuere GraphQL/MongoDB API ved hjelp av Netlify-funksjoner - The Codest
The Codest
  • Om oss
  • Tjenester
    • Programvareutvikling
      • Frontend-utvikling
      • Backend-utvikling
    • Staff Augmentation
      • Frontend-utviklere
      • Backend-utviklere
      • Dataingeniører
      • Ingeniører i skyen
      • QA-ingeniører
      • Annet
    • Det rådgivende
      • Revisjon og rådgivning
  • Industrier
    • Fintech og bankvirksomhet
    • E-commerce
    • Adtech
    • Helseteknologi
    • Produksjon
    • Logistikk
    • Bilindustrien
    • IOT
  • Verdi for
    • ADMINISTRERENDE DIREKTØR
    • CTO
    • Leveransesjef
  • Vårt team
  • Casestudier
  • Vet hvordan
    • Blogg
    • Møter
    • Webinarer
    • Ressurser
Karriere Ta kontakt med oss
  • Om oss
  • Tjenester
    • Programvareutvikling
      • Frontend-utvikling
      • Backend-utvikling
    • Staff Augmentation
      • Frontend-utviklere
      • Backend-utviklere
      • Dataingeniører
      • Ingeniører i skyen
      • QA-ingeniører
      • Annet
    • Det rådgivende
      • Revisjon og rådgivning
  • Verdi for
    • ADMINISTRERENDE DIREKTØR
    • CTO
    • Leveransesjef
  • Vårt team
  • Casestudier
  • Vet hvordan
    • Blogg
    • Møter
    • Webinarer
    • Ressurser
Karriere Ta kontakt med oss
Pil tilbake GÅ TILBAKE
2021-05-13
Programvareutvikling

Distribuere GraphQL/MongoDB API ved hjelp av Netlify-funksjoner

The Codest

Pawel Rybczynski

Software Engineer

Mål Innledende oppsett Installer avhengigheter La oss starte Først legger vi til tsconfig.json i hovedkatalogen: Nå oppretter vi src/server.ts for serverimplementeringer. Legg deretter til to funksjoner: en for lokal server og en for lambda. OK, vi har ingen resolvere eller typedefinisjoner, så vi må lage noen. La oss anta at vi først ønsker [...]

Målsettinger

  1. Konfigurer både lokale servere og lambda-servere.
  2. Koble begge til MongoDB.
  3. Implementer grunnleggende autentisering.
  4. Distribuere serverløse Apollo GraphQL API med Netlify.
  5. Bruk Typescript.

Første oppsett

npm init -y

Installer avhengigheter

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

La oss begynne

Først legger du til tsconfig.json til hovedkatalogen:

 {
 "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"]
 }

La oss nå opprette src/server.ts for serverimplementeringer. Legg deretter til to funksjoner: én for lokal server og én for lambda.

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


const createLambdaServer = () =>
  new ApolloServerLambda({
    typeDefs,
    resolvers,
    introspeksjon: true,
    playground: true,
    },
  });

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

export { createLambdaServer, createLocalServer };

OK, vi har ingen resolvere eller typedefinisjoner, så vi må lage noen. La oss anta at vi i første omgang ønsker å opprette brukere og motta informasjon om dem.

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

const userSchema = gql`
  type Bruker {
    id: ID!
    e-post: String!
    name: String!
  }

  type Query {
    user(id: ID!): Bruker!
  }

  type Mutasjon {
    createUser(navn: String!, e-post: String!, passord: String!): User!
  }
`;

Hvis du ikke er kjent med dette, Apollo utarbeidet en veldig fin opplæring

La oss nå opprette en brukeroppløser med én spørring og én mutasjon.

// src/resolvers.ts
const userResolver = {
  Query: {
    user: async (parent, args, context, info) => {
      {...}
    },
  },
  mutasjon: {
    createUser: async (overordnet, args, kontekst, info) => {
      {...}
    },
  },
};

Men vi har ingen data ... La oss fikse det 😉.

Ikke glem å importere typedefinisjon og resolver til serveren.

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

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

{...}

Koble til MongoDB via mongoose

Nå er det på tide å opprette en forbindelse til databasen vår. I dette tilfellet vil det være MongoDB. Den er gratis og enkel å vedlikeholde. Men før det, la oss installere to avhengigheter til:

npm install --save mongoose dotenv

Det første trinnet er å opprette en brukermodell.

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

export type User = Document & {
  _id: string
  e-post: string
  navn: string
  passord: string,
};

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

const UserSchema: Schema = new Schema({
  e-post: {
    type: String,
    required: true,
    unique: true,
  },
  name: {
    type: String,
    required: true,
    minLength: 3,
    maxLength: 32,
  },
  password: {
    type: String,
    required: true,
  },
});

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

Gjør passordene tryggere

Sikkerhet først! La oss nå sikre passordene våre ved å hashe dem.

npm install --save bcrypt @types/bcrypt

Nå implementerer du passordsikkerheten i tilbakekallingen før mellomvare. Pre-middleware-funksjoner kjøres etter hverandre, når hver mellomvare kaller neste gang. For å sikre passordene bruker vi en teknikk som genererer et salt og en hash ved separate funksjonsanrop.

// src/model.ts
importer 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);
      bruker.passord = hash;
      neste();
    });
  });
});
{...}

Legg deretter til metoden comparePasswords i 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);
  });
};
{...}

Og du kan selvfølgelig endre brukertype.

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

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

Nå kan vi opprette en forbindelse mellom servere og databaser. MONGODB_URI er en miljøvariabel som inneholder en tilkoblingsstreng som trengs for å opprette tilkoblingen. Du kan hente den fra klyngepanelet etter at du har logget inn på MongoDB Atlas-kontoen din. Sett den inn i .env

// .env
MONGODB_URI = ...;

Husk alltid å legge til filen i .gitignore. Flott! La oss nå legge til en funksjon som gjør det mulig å koble til db.

// src/server.ts
import mongoose, { Connection } fra "mongoose";
{...}
let 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å tvers av alle resolvere. For å tilby det trenger vi bare å legge til en kontekstinitialiseringsfunksjon i ApolloServer-konstruktøren. La oss gjøre det.

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

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

const createLocalServer = () =>
  new ApolloServer({
    typeDefs,
    resolvers,
    introspeksjon: true,
    lekeplass: true,
    context: async () => {
      await connectToDatabase();

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

Som du kan se, sender vi også userModel gjennom sammenheng. Vi kan nå oppdatere resolveren:

// resolvers.ts
const userResolver = {
  Query: {
    user: async (_, { email, name, password }, { models: { userModel } }) => {
      const user = await userModel.findById({ _id: id }).exec();
      return user;
    },
  },
  Mutasjon: {
    createUser: async (_, { id }, { models: { userModel } }) => {
      const user = await userModel.create({ e-post, navn, passord });
      return user;
    },
  },
};

Det ser bra ut! Opprett nå en grunnleggende 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 kjører på ${url}`);
});

Starte den lokale serveren med Nodemon

Siste ting før kjøring, legg til nodemon.json

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

Legg til skript i package.json

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

Og løp!

npm start

Du bør få slik informasjon inne i terminalen:

Server ir kjører på http://localhost:4000/

Hvis ja, åpner du http://localhost:4000/ i nettleseren din.

GraphQL-lekeplassen bør vises. La oss opprette en ny bruker!

createUser.png

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

databaseJohnDoe.png

Kult! Alt fungerer fint!

La oss prøve å få litt brukerinformasjon.

userWithoutAuth.png

Og legg til passordfeltet...

userPassword.png

Bra! Vi mottar feil Kan ikke søke i feltet "password" på typen "User".". Som du kan se, har vi ikke lagt til dette feltet i brukertypedefinisjonen. Det er der med vilje. Vi skal ikke gjøre det mulig å spørre etter passord eller andre sensitive data.

En annen ting ... Vi kan få brukerdata uten autentisering ... det er ikke en god løsning. Vi må fikse det.

Men før...

Konfigurere Codegen

La oss bruke GraphQL kode generatoren for å få en kompatibel basistype, basert på skjemaet vårt.

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

Opprett codegen.yml

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

Legg til kodegen skript til package.json

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

Kjør deretter skriptet når den lokale serveren kjører:

npm run codegen

Hvis du lykkes, får du en melding:

  √ Parse konfigurasjon
  √ Generer utganger

Hvis du mottar denne informasjonen, skal to filer vises:

  • graphql.schema.json i hovedkatalogen
  • graphql.ts i nyopprettet sti src/generert

Vi er mer interessert i den andre. Hvis du åpner den, vil du legge merke til en fin struktur av typer.

Nå kan vi forbedre resolverne våre:

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

const userResolver: Resolvers = {
  Query: {
    user: async (_, { id }, { models: { userModel }, auth }): Promise => {
      const user = await userModel.findById({ _id: id }).exec();
      return user;
    },
  },
  Mutasjon: {
    createUser: async (
      _,
      { e-post, navn, passord },
      { models: { userModel } }
    ): Promise => {
      const user = await userModel.create({
        e-post,
        name,
        passord,
      });
      return user;
    },
  },
};

Autentisering

La oss nå sette opp en enkel tokenbasert autentisering.

npm install --save jsonwebtoken @types/jsonwebtoken

Opprett checkAuth funksjon for å verifisere om tokenet er gyldig. Vi legger til resultatet i konteksten, slik at vi kan få tilgang 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 trenger også en måte å opprette et slikt token på. Den beste måten er å implementere et påloggingsspørsmål i resolveren vår.

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

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

      const user = await userModel.findById({ _id: id }).exec();
      return user;
    },
    login: async (
      _,
      { e-post, passord },
      { models: { userModel } }
    ): Promise => {
      const user = await userModel.findOne({ email }).exec();

      if (!user) throw new AuthenticationError("Ugyldig legitimasjon");

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

      if (!matchPasswords) kast new AuthenticationError("Ugyldig legitimasjon");

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

      return { token };
    },
  },
  Mutasjon: {
    createUser: async (
      _,
      { e-post, navn, passord },
      { models: { userModel } }
    ): Promise => {
      const user = await userModel.create({
        e-post,
        name,
        passord,
      });
      return user;
    },
  },
};

Du må også oppdatere brukertypedefinisjonene etter Token-type og påloggingsspørsmål.

type Token {
    token: String!
  }

type Query {
    user(id: ID!): User!
    login(e-post: String!, passord: String!): Token!
  }

La oss nå prøve å hente brukeren uten token

noAuth.png

OK, fungerer fint! Prøv å logge inn

token.png

La oss prøve igjen for å hente brukeren, men denne gangen med token lagt til i overskriftene

withToken.png

Kult! Og hva om vi angir feil legitimasjon?

wrongEmail.png

Fint!

Gjør deg klar til å distribuere

Siste trinn: distribuer serverløs api med Netlify!

Opprett mappe lambda i hoveddirectoren og legg inn to filer:

Første inneholder AWS handler. Den oppretter en ApolloServerLambda-serverinstans og
og deretter eksponere en handler ved hjelp av createHandler for den instansen.

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

export const handler = async (
  event: APIGatewayProxyEvent,
  context: Context
) => {
  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 lese mer om det.

Den andre er tsconfig. En viktig del er outDir felt.

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

Men det er et problem. Vi kan ikke bruke /src dir fordi Netlify ikke kan nå utenfor lambda-mappen. Så vi må pakke den sammen.

npm install --save ncp

Det er en pakke som gjør det mulig å kopiere katalog.

Legg til pakke skript til package.json

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

Hvis du nå kjører npm kjør pakkekan du se at i nyopprettede lambda/bundle dir har vi alle filene fra src/.

Oppdater importstien inne i lambda/graphql.ts

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

{...}

Du kan nå legge til lambda/bundle dir til .gitignore

Distribuere med Netlify

Vi må fortelle Netlify hva byggekommandoen er og hvor funksjonene våre bor. For å gjøre det, la oss opprette netlify.toml file:

// netlify.toml
[build]
  command = "npm run build:lambda"
  functions = "lambda/dist"

Som du kan se, er det samme katalog som er definert som en outDir feltet i lambda/tsconfig.json

Slik bør appstrukturen din se ut (vel... en del av 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

Legg til bundle:lambda skript til package.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",
  },

Distribuere

La oss prøve å distribuere appen vår via Netlify.

  1. Logg på Netlify-kontoen din,
  2. Koble deg til Github,
  3. Klikk på knappen "Nytt nettsted fra Git",
  4. Velg et riktig repo,
  5. Angi byggekommandoen npm run build:lambda,
  6. Legg til miljøvariabel (MONGODB_URI),
  7. Distribuere...

Og TAAADAAAAAA...

pageNotFound.png

Det er fordi endepunktet som standard ikke er http://page/ men http://page/.netlify/functions/graphql.

withoutRedirect.png

Hvordan fikser jeg det? Det er veldig enkelt. Bare opprett _Viderekoblinger med:

/ /.netlify/functions/graphql 200!

Distribuer igjen og sjekk.

redirect.png

Håper du likte den! Du er velkommen til å forbedre og endre.

Les mer om dette:

Hvordan unngår man å drepe et prosjekt med dårlig kodingspraksis?

Sikkerhet i webapplikasjoner. Target="_blank" sårbarhet

Sikkerhet i webapplikasjoner - XSS-sårbarhet

Relaterte artikler

Programvareutvikling

Bygg fremtidssikre webapper: Innsikt fra The Codests ekspertteam

Oppdag hvordan The Codest utmerker seg når det gjelder å skape skalerbare, interaktive webapplikasjoner med banebrytende teknologi som gir sømløse brukeropplevelser på tvers av alle plattformer. Finn ut hvordan ekspertisen vår driver digital transformasjon og...

THECODEST
Programvareutvikling

Topp 10 Latvia-baserte programvareutviklingsselskaper

I vår nyeste artikkel kan du lese mer om Latvias beste programvareutviklingsselskaper og deres innovative løsninger. Oppdag hvordan disse teknologilederne kan bidra til å løfte virksomheten din.

thecodest
Løsninger for bedrifter og oppskalering

Grunnleggende om Java-programvareutvikling: En guide til vellykket outsourcing

Utforsk denne viktige veiledningen om vellykket outsourcing av Java-programvareutvikling for å øke effektiviteten, få tilgang til ekspertise og drive frem prosjektsuksess med The Codest.

thecodest
Programvareutvikling

Den ultimate guiden til outsourcing i Polen

Den kraftige økningen i outsourcing i Polen er drevet av økonomiske, utdanningsmessige og teknologiske fremskritt, noe som fremmer IT-vekst og et forretningsvennlig klima.

TheCodest
Løsninger for bedrifter og oppskalering

Den komplette guiden til verktøy og teknikker for IT-revisjon

IT-revisjoner sørger for sikre, effektive og kompatible systemer. Les hele artikkelen for å lære mer om viktigheten av dem.

The Codest
Jakub Jakubowicz CTO og medgrunnlegger

Abonner på vår kunnskapsbase og hold deg oppdatert på ekspertisen fra IT-sektoren.

    Om oss

    The Codest - Internasjonalt programvareutviklingsselskap med teknologisentre i Polen.

    Storbritannia - Hovedkvarter

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

    Polen - Lokale teknologisentre

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

      The Codest

    • Hjem
    • Om oss
    • Tjenester
    • Casestudier
    • Vet hvordan
    • Karriere
    • Ordbok

      Tjenester

    • Det rådgivende
    • Programvareutvikling
    • Backend-utvikling
    • Frontend-utvikling
    • Staff Augmentation
    • Backend-utviklere
    • Ingeniører i skyen
    • Dataingeniører
    • Annet
    • QA-ingeniører

      Ressurser

    • Fakta og myter om samarbeid med en ekstern programvareutviklingspartner
    • Fra USA til Europa: Hvorfor velger amerikanske oppstartsbedrifter å flytte til Europa?
    • Sammenligning av Tech Offshore Development Hubs: Tech Offshore Europa (Polen), ASEAN (Filippinene), Eurasia (Tyrkia)
    • Hva er de største utfordringene for CTO-er og CIO-er?
    • The Codest
    • The Codest
    • The Codest
    • Retningslinjer for personver
    • Vilkår for bruk av nettstedet

    Opphavsrett © 2025 av The Codest. Alle rettigheter forbeholdt.

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