The Codest
  • O nás
  • Služby
    • Vývoj softwaru
      • Vývoj frontendů
      • Vývoj backendu
    • Staff Augmentation
      • Vývojáři frontendů
      • Vývojáři backendu
      • Datoví inženýři
      • Cloudoví inženýři
      • Inženýři QA
      • Další
    • To Advisory
      • Audit a poradenství
  • Odvětví
    • Fintech a bankovnictví
    • E-commerce
    • Adtech
    • Healthtech
    • Výroba
    • Logistika
    • Automobilový průmysl
    • IOT
  • Hodnota za
    • CEO
    • CTO
    • Manažer dodávek
  • Náš tým
  • Case Studies
  • Vědět jak
    • Blog
    • Setkání
    • Webové semináře
    • Zdroje
Kariéra Spojte se s námi
  • O nás
  • Služby
    • Vývoj softwaru
      • Vývoj frontendů
      • Vývoj backendu
    • Staff Augmentation
      • Vývojáři frontendů
      • Vývojáři backendu
      • Datoví inženýři
      • Cloudoví inženýři
      • Inženýři QA
      • Další
    • To Advisory
      • Audit a poradenství
  • Hodnota za
    • CEO
    • CTO
    • Manažer dodávek
  • Náš tým
  • Case Studies
  • Vědět jak
    • Blog
    • Setkání
    • Webové semináře
    • Zdroje
Kariéra Spojte se s námi
Šipka zpět ZPĚT
2021-05-13
Vývoj softwaru

Nasazení rozhraní API GraphQL/MongoDB pomocí funkcí Netlify

The Codest

Pawel Rybczynski

Software Engineer

Cíle Počáteční nastavení Instalace závislostí Začněme Nejprve přidejte soubor tsconfig.json do hlavního adresáře: Nyní vytvořme src/server.ts pro implementaci serverů. Poté přidáme dvě funkce: jednu pro lokální server a druhou pro lambdu. OK, nemáme žádné resolvery ani definice typů, takže je musíme vytvořit. Předpokládejme, že nejprve chceme [...]

Cíle

  1. Konfigurace místních serverů i serverů lambda.
  2. Připojte obě k MongoDB.
  3. Implementujte základní ověřování.
  4. Nasazení bezserverového systému Apollo GraphQL API s Netlify.
  5. Použití Typescriptu.

Počáteční nastavení

npm init -y

Instalace závislostí

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

Začněme

Nejprve přidejte tsconfig.json do hlavního adresáře:

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

Nyní vytvořme src/server.ts pro implementace serverů. Pak přidejte dvě funkce: jednu pro místní server a druhou pro lambdu.

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


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

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

export { createLambdaServer, createLocalServer };

OK, nemáme žádné resolvery ani definice typů, takže je musíme vytvořit. Předpokládejme, že nejprve chceme vytvářet uživatele a přijímat o nich informace.

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

const userSchema = gql`
  typ User {
    id: ID!
    email: String!
    name: String!
  }

  typ Query {
    user(id: ID!): User!
  }

  typ Mutace {
    createUser(name: String!, email: String!, password: String!): User!
  }
`;

Pokud to neznáte, Apollo připravil velmi pěkný výukový program

Nyní vytvoříme uživatelský resolver s jedním dotazem a jednou mutací.

// src/resolvers.ts
const userResolver = {
  Dotaz: {
    user: async (parent, args, context, info) => {
      {...}
    },
  },
  Mutace: {
    createUser: async (parent, args, context, info) => {
      {...}
    },
  },
};

Ale nemáme žádná data... Pojďme to napravit 😉

Nezapomeňte importovat definici typu a resolver na server.

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

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

{...}

Připojení k MongoDB prostřednictvím mongoose

Nyní je vhodný čas vytvořit spojení s naší databází. V tomto konkrétním případě to bude MongoDB. Je zdarma a snadno se udržuje. Ještě předtím však nainstalujme další dvě závislosti:

npm install --save mongoose dotenv

Prvním krokem je vytvoření uživatelského modelu.

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

export type User = Document & {
  _id: string,
  email: string,
  name: string,
  password: string,
};

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

const UserSchema: Schema = new Schema({
  email: {
    typ: : String,
    required: true,
    unique: true,
  },
  name: {
    type: :: String,
    required: true,
    minLength: 3,
    maxLength: 32,
  },
  heslo: {
    typ: : String,
    required: true,
  },
});

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

Zvýšení bezpečnosti hesel

Bezpečnost především! Nyní zabezpečíme naše hesla pomocí hashování.

npm install --save bcrypt @types/bcrypt

Nyní implementujte zabezpečení hesla v rámci zpětného volání pre-middleware. Funkce pre-middlewaru se provádějí jedna po druhé, když každý middleware volá další. Pro zabezpečení hesel používáme techniku, která generuje sůl a hash při samostatných voláních funkcí.

// 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();
    });
  });
});
{...}

Pak přidejte metodu comparePasswords do 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);
  });
};
{...}

A samozřejmě upravte typ uživatele.

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

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

Nyní můžeme zajistit spojení mezi servery a databázemi. MONGODB_URI je proměnná prostředí, která obsahuje připojovací řetězec potřebný k vytvoření připojení. Můžete jej získat z panelu clusteru po přihlášení k účtu MongoDB atlas. Vložte ji dovnitř .env

// .env
MONGODB_URI = ...;

Vždy nezapomeňte přidat tento soubor do .gitignore. Skvělé! Nyní přidáme funkci, která umožní spojení s db.

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

Kontext je objekt, který je sdílený všemi resolvery. Abychom jej mohli poskytnout, stačí přidat inicializační funkci kontextu do konstruktoru ApolloServeru. Udělejme to.

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

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

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

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

Jak vidíte, předáváme také userModel prostřednictvím kontext. Nyní můžeme aktualizovat resolver:

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

Vypadá to pěkně! Nyní vytvořte základní instanci serveru:

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

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

const server = createLocalServer();

server.listen(port).then(({ url }) => {
  console.log(`Server ir běží na adrese ${url}`);
});

Spuštění místního serveru pomocí Nodemonu

Poslední věc před spuštěním, přidejte nodemon.json

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

Přidání skriptu do package.json

"skripty": {
    "start": "nodemon"
  },

A utíkejte!

npm start

Tyto informace byste měli získat v terminálu:

Server ir běží na adrese http://localhost:4000/

Pokud ano, otevřete http://localhost:4000/ v prohlížeči.

Mělo by se zobrazit hřiště GraphQL. Vytvořme nového uživatele!

createUser.png

Podívejte se, jak to vypadá v databázi.

databaseJohnDoe.png

Super! Všechno funguje dobře!

Zkusme získat nějaké informace o uživateli.

userWithoutAuth.png

A přidejte pole hesla...

userPassword.png

Pěkné! Obdržíme chybu Nelze zadat dotaz do pole "heslo" na typu "Uživatel".". Jak si můžete zpětně ověřit, nepřidali jsme toto pole do definice typu uživatele. Je tam záměrně. Neměli bychom umožnit dotazování na heslo nebo jiné citlivé údaje.

Další věc... Můžeme získat uživatelská data bez jakéhokoli ověření... to není dobré řešení. Musíme to opravit.

Ale ještě předtím...

Konfigurace aplikace Codegen

Použijme GraphQL kód získat kompatibilní základní typ na základě našeho schématu.

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

Vytvořit codegen.yml

přepsat: true
schéma: "http://localhost:4000"
generuje:
  ./src/generated/graphql.ts:
    plugins:
      - "typescript"
      - "typescript-resolvers"
  ./graphql.schema.json:
    plugins:
      - "introspection"

Přidat codegen skript na package.json

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

Když je místní server spuštěn, spusťte skript:

npm run codegen

V případě úspěchu se zobrazí zpráva:

  √ Konfigurace parsování
  √ Generování výstupů

Pokud tyto informace obdržíte, měly by se objevit dva soubory:

  • graphql.schema.json v hlavním adresáři
  • graphql.ts v nově vytvořené cestě src/generated

Nás zajímá spíše ta druhá. Pokud ji otevřete, všimnete si pěkné struktury typů.

Nyní můžeme vylepšit naše resolvery:

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

const userResolver: Resolvers = {
  Query: {
    user: async (_, { id }, { models: { userModel }, auth }): Promise => {
      const user = await userModel.findById({ _id: id }).exec();
      return user;
    },
  },
  Mutace: {
    createUser: async (
      _,
      { email, name, password },
      { models: { userModel } }
    ): Promise => {
      const user = await userModel.create({
        email,
        name,
        password,
      });
      return user;
    },
  },
};

Ověřování

Dále nastavíme jednoduché ověřování pomocí tokenu.

npm install --save jsonwebtoken @types/jsonwebtoken

Vytvořit checkAuth ověřit, zda je token platný. Výsledek přidáme do kontextu, abychom k němu mohli přistupovat uvnitř resolverů.

// 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,
        },
      };
    },
  });
}

Také potřebujeme způsob, jak takový token vytvořit. Nejlepším způsobem je implementovat přihlašovací dotaz uvnitř našeho resolveru.

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

const userResolver: Resolvers = {
  Query: {
    user: async (_, { id }, { models: { userModel }, auth }): Promise => {
      if (!auth) throw new AuthenticationError("Nejste ověřeni");

      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("Neplatné pověření");

      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 };
    },
  },
  Mutace: {
    createUser: async (
      _,
      { email, name, password },
      { models: { userModel } }
    ): Promise => {
      const user = await userModel.create({
        email,
        name,
        password,
      });
      return user;
    },
  },
};

Je také třeba aktualizovat definice typu uživatele podle typu tokenu a přihlašovacího dotazu.

typ Token {
    token: String!
  }

typ Query {
    user(id: ID!): User!
    login(email: String!, password: String!): Token!
  }

Zkusme nyní získat uživatele bez tokenu

noAuth.png

OK, funguje dobře! Zkuste se přihlásit

token.png

Zkusme znovu získat uživatele, ale tentokrát s tokenem přidaným do hlaviček

withToken.png

Super! A co když nastavíme špatné pověření?

wrongEmail.png

Pěkné!

Příprava na nasazení

Poslední krok: nasazení serverless api pomocí Netlify!

Vytvořit složku lambda v hlavním adresáři a vložte do něj dva soubory:

První obsahuje AWS obsluha. Vytvoří instanci serveru ApolloServerLambda a
pak vystavit obslužnou rutinu pomocí createHandler této instance.

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

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);
  });
};

Můžete si o něm přečíst více.

Druhým je tsconfig. Důležitou součástí je outDir pole.

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

Je tu však problém. Nemůžeme použít /src dir, protože Netlify nemůže dosáhnout mimo složku lambda. Musíme ji tedy svázat do balíčku.

npm install --save ncp

Je to balíček, který umožňuje kopírovat adresáře.

Přidat svazek skript na package.json

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

Pokud nyní spustíte npm run bundle, můžete vidět, že v nově vytvořeném lambda/bundle dir máme všechny soubory z src/.

Aktualizujte cestu importu uvnitř lambda/graphql.ts

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

{...}

Nyní můžete přidat lambda/bundle dir do adresáře .gitignore

Nasazení pomocí Netlify

Musíme společnosti Netlify sdělit, jaký je příkaz pro sestavení a kde se nacházejí naše funkce. Za tímto účelem vytvořme netlify.toml file:

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

Jak vidíte, jedná se o stejný adresář, který je definován jako outDir pole v lambda/tsconfig.json

Takto by měla vypadat struktura vaší aplikace (tedy... její část ;))

aplikace
└───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

Přidat bundle:lambda skript na package.json

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

Nasazení

Zkusme nasadit naši aplikaci prostřednictvím služby Netlify.

  1. Přihlaste se ke svému účtu Netlify,
  2. Připojte se ke svému Githubu,
  3. Klikněte na tlačítko "Nový web ze systému Git",
  4. Vyberte správné úložiště,
  5. Nastavte příkaz pro sestavení npm run build:lambda,
  6. Přidání proměnné prostředí (MONGODB_URI),
  7. Nasazení...

A TAAADAAAA...

pageNotFound.png

Je to proto, že koncový bod není ve výchozím nastavení http://page/ ale http://page/.netlify/functions/graphql.

withoutRedirect.png

Jak to napravit? Je to velmi jednoduché. Stačí vytvořit _redirects s:

/ /.netlify/functions/graphql 200!

Znovu nasaďte a zkontrolujte.

redirect.png

Doufám, že se vám to líbilo! Nebojte se vylepšovat a měnit.

Přečtěte si více:

Jak nezničit projekt špatnými kódovacími postupy?

Zabezpečení webových aplikací. Target="_blank" zranitelnost

Zabezpečení webových aplikací - zranitelnost XSS

Související články

Ilustrace zdravotnické aplikace pro chytré telefony s ikonou srdce a rostoucím zdravotním grafem, označená logem The Codest, která představuje digitální zdraví a řešení HealthTech.
Vývoj softwaru

Softwarové vybavení pro zdravotnictví: a případy použití

Nástroje, na které se dnes zdravotnické organizace spoléhají, se v ničem nepodobají papírovým kartám z doby před desítkami let. zdravotnický software dnes podporuje zdravotnické systémy, péči o pacienty a moderní poskytování zdravotní péče v klinických a...

NEJKRÁSNĚJŠÍ
Abstraktní ilustrace klesajícího sloupcového grafu se stoupající šipkou a zlatou mincí symbolizující efektivitu nákladů nebo úspory. V levém horním rohu se zobrazuje logo The Codest se sloganem "In Code We Trust" na světle šedém pozadí.
Vývoj softwaru

Jak rozšířit tým vývojářů bez ztráty kvality produktu

Zvětšujete svůj vývojový tým? Zjistěte, jak růst, aniž byste museli obětovat kvalitu produktu. Tento průvodce se zabývá příznaky, že je čas na škálování, strukturou týmu, najímáním zaměstnanců, vedením a nástroji - a také tím, jak může The Codest...

NEJKRÁSNĚJŠÍ
Vývoj softwaru

Vytváření webových aplikací odolných vůči budoucnosti: postřehy týmu odborníků The Codest

Zjistěte, jak společnost The Codest vyniká při vytváření škálovatelných, interaktivních webových aplikací pomocí nejmodernějších technologií, které poskytují bezproblémové uživatelské prostředí na všech platformách. Zjistěte, jak naše odborné znalosti podporují digitální transformaci a obchodní...

NEJKRÁSNĚJŠÍ
Vývoj softwaru

10 nejlepších lotyšských společností zabývajících se vývojem softwaru

V našem nejnovějším článku se dozvíte o nejlepších lotyšských společnostech zabývajících se vývojem softwaru a jejich inovativních řešeních. Zjistěte, jak mohou tito technologičtí lídři pomoci pozvednout vaše podnikání.

thecodest
Podniková a škálovací řešení

Základy vývoje softwaru v jazyce Java: A Guide to Outsourcing Successfully

Prozkoumejte tuto základní příručku o úspěšném vývoji softwaru outsourcing Java, abyste zvýšili efektivitu, získali přístup k odborným znalostem a dosáhli úspěchu projektu s The Codest.

thecodest

Přihlaste se k odběru naší znalostní databáze a získejte aktuální informace o odborných znalostech z oblasti IT.

    O nás

    The Codest - Mezinárodní společnost zabývající se vývojem softwaru s technologickými centry v Polsku.

    Spojené království - ústředí

    • Kancelář 303B, 182-184 High Street North E6 2JA
      Londýn, Anglie

    Polsko - Místní technologická centra

    • Kancelářský park Fabryczna, Aleja
      Pokoju 18, 31-564 Krakov
    • Brain Embassy, Konstruktorska
      11, 02-673 Varšava, Polsko

      The Codest

    • Home
    • O nás
    • Služby
    • Case Studies
    • Vědět jak
    • Kariéra
    • Slovník

      Služby

    • To Advisory
    • Vývoj softwaru
    • Vývoj backendu
    • Vývoj frontendů
    • Staff Augmentation
    • Vývojáři backendu
    • Cloudoví inženýři
    • Datoví inženýři
    • Další
    • Inženýři QA

      Zdroje

    • Fakta a mýty o spolupráci s externím partnerem pro vývoj softwaru
    • Z USA do Evropy: Proč se americké startupy rozhodly přesídlit do Evropy?
    • Srovnání technických vývojových center v zahraničí: Tech Offshore Evropa (Polsko), ASEAN (Filipíny), Eurasie (Turecko)
    • Jaké jsou hlavní výzvy CTO a CIO?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • Website terms of use

    Copyright © 2026 by The Codest. Všechna práva vyhrazena.

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