window.pipedriveLeadboosterConfig = { base: 'leadbooster-chat.pipedrive.com', companyId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version: 2, } ;(function () { var w = finestra if (w.LeadBooster) { console.warn('LeadBooster esiste già') } 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 }) }, } } })() Distribuzione di API GraphQL/MongoDB utilizzando le funzioni Netlify - The Codest
The Codest
  • Chi siamo
  • Servizi
    • Sviluppo di software
      • Sviluppo Frontend
      • Sviluppo backend
    • Staff Augmentation
      • Sviluppatori Frontend
      • Sviluppatori backend
      • Ingegneri dei dati
      • Ingegneri del cloud
      • Ingegneri QA
      • Altro
    • Consulenza
      • Audit e consulenza
  • Industrie
    • Fintech e banche
    • E-commerce
    • Adtech
    • Tecnologia della salute
    • Produzione
    • Logistica
    • Automotive
    • IOT
  • Valore per
    • CEO
    • CTO
    • Responsabile della consegna
  • Il nostro team
  • Case Studies
  • Sapere come
    • Blog
    • Incontri
    • Webinar
    • Risorse
Carriera Contattate
  • Chi siamo
  • Servizi
    • Sviluppo di software
      • Sviluppo Frontend
      • Sviluppo backend
    • Staff Augmentation
      • Sviluppatori Frontend
      • Sviluppatori backend
      • Ingegneri dei dati
      • Ingegneri del cloud
      • Ingegneri QA
      • Altro
    • Consulenza
      • Audit e consulenza
  • Valore per
    • CEO
    • CTO
    • Responsabile della consegna
  • Il nostro team
  • Case Studies
  • Sapere come
    • Blog
    • Incontri
    • Webinar
    • Risorse
Carriera Contattate
Freccia indietro TORNA INDIETRO
2021-05-13
Sviluppo di software

Distribuire API GraphQL/MongoDB utilizzando le funzioni Netlify

The Codest

Pawel Rybczynski

Software Engineer

Obiettivi Impostazione iniziale Installazione delle dipendenze Iniziamo Per prima cosa, aggiungiamo il file tsconfig.json alla cartella principale: Ora, creiamo src/server.ts per le implementazioni dei server. Poi aggiungiamo due funzioni: una per il server locale e l'altra per lambda. Ok, non abbiamo risolutori o definizioni di tipi, quindi dobbiamo crearne alcuni. Supponiamo che, all'inizio, vogliamo [...]

Obiettivi

  1. Configurare i server locali e lambda.
  2. Collegare entrambi a MongoDB.
  3. Implementare l'autenticazione di base.
  4. Distribuire Apollo senza server GraphQL API con Netlify.
  5. Usare il dattiloscritto.

Impostazione iniziale

npm init -y

Installare le dipendenze

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

Iniziamo

Per prima cosa, aggiungere il file tsconfig.json alla directory principale:

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

Ora, creiamo src/server.ts per le implementazioni dei server. Quindi aggiungere due funzioni: una per il server locale e la seconda per lambda.

// src/server.ts
importare { ApolloServer as ApolloServerLambda } da "apollo-server-lambda";
importare { ApolloServer } da "apollo-server";


const createLambdaServer = () =>
  nuovo ApolloServerLambda({
    typeDefs,
    resolver,
    introspezione: true,
    playground: true,
    },
  });

const createLocalServer = () =>
  nuovo ApolloServer({
    typeDefs,
    resolver,
    introspezione: true,
    playground: true,
    },
  });

esportare { createLambdaServer, createLocalServer };

Ok, non abbiamo nessun resolver o definizione di tipo, quindi dobbiamo crearne qualcuno. Supponiamo che, all'inizio, vogliamo creare utenti e ricevere informazioni su di loro.

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

const userSchema = gql`
  tipo Utente {
    id: ID!
    email: String!
    name: String!
  }

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

  tipo Mutazione {
    createUser(nome: String!, email: String!, password: String!): User!
  }
`;

Se non ne siete a conoscenza, Apollo ha preparato un tutorial molto bello

Ora creiamo un risolutore di utenti con una query e una mutazione.

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

Ma non abbiamo alcun dato... Risolviamo il problema 😉

Non dimenticare di importare la definizione del tipo e il resolver sul server.

// src/server.ts
importare { ApolloServer as ApolloServerLambda } da "apollo-server-lambda";
importare { ApolloServer } da "apollo-server";

importare { typeDefs } da "./schemas";
importare { resolvers } da "./resolvers";

{...}

Connettersi con MongoDB tramite mongoose

Ora è il momento di creare una connessione con il nostro database. In questo caso particolare, si tratta di MongoDB. È gratuito e facile da mantenere. Ma prima di questo, installiamo altre due dipendenze:

npm install --save mongoose dotenv

Il primo passo consiste nel creare un Modello utente.

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

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

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

const UserSchema: Schema = new Schema({
  email: {
    tipo: String,
    required: true,
    unique: true,
  },
  nome: {
    tipo: String,
    required: true,
    minLunghezza: 3,
    maxLunghezza: 32,
  },
  password: {
    tipo: String,
    required: true,
  },
});

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

Rendere le password più sicure

La sicurezza prima di tutto! Ora proteggiamo le nostre password con l'hashing.

npm install --save bcrypt @types/bcrypt

Ora, implementare la sicurezza della password nel callback del pre-middleware. Le funzioni pre-middleware vengono eseguite una dopo l'altra, quando ogni middleware chiama il successivo. Per rendere sicure le password, utilizziamo una tecnica che genera un sale e un hash in chiamate di funzione separate.

// src/model.ts
importare bcrypt da "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(password.utente, sale, function (err, hash) {
      if (err) return next(err);
      user.password = hash;
      next();
    });
  });
});
{...}

Aggiungere quindi il metodo comparePasswords a 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);
  });
};
{...}

E, naturalmente, modificare il tipo di utente.

tipo comparePasswordFunction = (
  candidatePassword: string,
  cb: (err: errore, isMatch: booleano) => void
) => void;

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

Ora possiamo organizzare una connessione tra i server e i database. MONGODB_URI è una variabile d'ambiente che contiene una stringa di connessione necessaria per creare la connessione. È possibile ottenerla dal pannello del cluster dopo aver effettuato l'accesso al proprio account MongoDB atlas. Inserirla all'interno di .env

// .env
MONGODB_URI = ...;

Ricordate sempre di aggiungere il file a .gitignore. Ottimo! Ora aggiungiamo una funzione che permetta di connettersi al db.

// src/server.ts
import mongoose, { Connection } da "mongoose";
{...}
let cachedDb: Connection;

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

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

Il contesto è un oggetto condiviso da tutti i risolutori. Per fornirlo, basta aggiungere una funzione di inizializzazione del contesto al costruttore di ApolloServer. Facciamolo.

// src/server.ts
import { userModel } da "./models/user.model";
{...}
const createLambdaServer = async () =>
  nuovo ApolloServerLambda({
    typeDefs,
    resolver,
    introspezione: true,
    playground: true,
    contesto: async () => {
      await connectToDatabase();

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

const createLocalServer = () =>
  nuovo ApolloServer({
    typeDefs,
    resolver,
    introspezione: true,
    playground: true,
    contesto: async () => {
      await connectToDatabase();

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

Come si può vedere, passiamo anche modello utente attraverso contesto. Ora possiamo aggiornare il resolver:

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

Sembra bello! Ora create un'istanza del server di base:

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

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

const server = createLocalServer();

server.listen(port).then(({ url }) => {
  console.log(`Server ir in esecuzione a ${url}`);
});

Avviare il server locale con Nodemon

Come ultima cosa prima dell'esecuzione, aggiungere nodemon.json

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

Aggiungere lo script a pacchetto.json

"script": {
    "start": "nodemon"
  },

E corri!

npm start

Dovreste ottenere queste informazioni all'interno del terminale:

Server ir in esecuzione a http://localhost:4000/

In caso affermativo, aprire il file http://localhost:4000/ all'interno del browser.

Dovrebbe apparire l'area di gioco GraphQL. Creiamo un nuovo utente!

creareUtente.png

Date un'occhiata a come appare nel database.

databaseJohnDoe.png

Fantastico! Tutto funziona bene!

Cerchiamo di ottenere alcune informazioni sull'utente.

utenteSenzaAut.png

E aggiungere il campo della password...

userPassword.png

Bello! Riceviamo un errore Impossibile interrogare il campo "password" sul tipo "Utente"".. Come si può verificare, non abbiamo aggiunto questo campo nella definizione del tipo di utente. È lì di proposito. Non dobbiamo rendere possibile l'interrogazione di password o altri dati sensibili.

Un'altra cosa... Possiamo ottenere i dati degli utenti senza alcuna autenticazione... non è una buona soluzione. Dobbiamo risolvere il problema.

Ma prima...

Configurare Codegen

Utilizziamo il GraphQL codice per ottenere un tipo di base compatibile, basato sul nostro schema.

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

Creare codegen.yml

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

Aggiungi codegen script per pacchetto.json

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

Quindi, quando il server locale è in esecuzione, eseguire lo script:

npm run codegen

In caso di successo, verrà visualizzato il messaggio:

  √ Analizza la configurazione
  √ Generare uscite

Se si riceve questa informazione, dovrebbero apparire due file:

  • graphql.schema.json nella directory principale
  • graphql.ts nel percorso appena creato src/generato

Siamo più interessati al secondo. Se lo aprite, noterete una bella struttura di tipi.

Ora possiamo migliorare i nostri risolutori:

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

const userResolver: Resolvers = {
  Query: {
    user: async (_, { id }, { models: { userModel }, auth }): Promise => {
      const user = await userModel.findById({ _id: id }).exec();
      restituisce l'utente;
    },
  },
  Mutazione: {
    createUser: async (
      _,
      { email, nome, password },
      { models: { userModel } }
    ): Promessa => {
      const user = await userModel.create({
        email,
        nome,
        password,
      });
      restituisce l'utente;
    },
  },
};

Autenticazione

Quindi, impostiamo una semplice autenticazione basata su token.

npm install --save jsonwebtoken @types/jsonwebtoken

Creare checkAuth per verificare se il token è valido. Aggiungeremo il risultato al contesto, in modo da potervi accedere all'interno dei 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,
        },
      };
    },
  });
}

Inoltre, abbiamo bisogno di un modo per creare tale token. Il modo migliore è implementare una query di login all'interno del nostro resolver.

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

const userResolver: Resolvers = {
  Query: {
    user: async (_, { id }, { models: { userModel }, auth }): Promise => {
      if (!auth) throw new AuthenticationError("L'utente non è autenticato");

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

      if (!user) throw new AuthenticationError("Credenziali non valide");

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

      if (!matchPasswords) throw new AuthenticationError("Credenziali non valide");

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

      restituisce { token };
    },
  },
  Mutazione: {
    createUser: async (
      _,
      { email, nome, password },
      { models: { userModel } }
    ): Promessa => {
      const user = await userModel.create({
        email,
        nome,
        password,
      });
      restituisce l'utente;
    },
  },
};

È inoltre necessario aggiornare le definizioni dei tipi di utente per tipo di token e query di accesso.

tipo Token {
    token: String!
  }

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

Proviamo ora a ottenere un utente senza token

noAuth.png

OK, funziona bene! Prova ad accedere

token.png

Proviamo di nuovo a ottenere l'utente, ma questa volta con il token aggiunto agli header

conToken.png

Forte! E se impostiamo le credenziali sbagliate?

wrongEmail.png

Bello!

Prepararsi alla distribuzione

Ultimo passo: distribuire le API serverless con Netlify!

Creare una cartella lambda nella cartella principale e inserire due file al suo interno:

La prima contiene AWS gestore. Crea un'istanza di server ApolloServerLambda e
quindi esporre un gestore utilizzando createHandler di tale istanza.

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

export const handler = async (
  evento: APIGatewayProxyEvent,
  contesto: Contesto
) => {
  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);
  });
};

Per saperne di più.

Il secondo è tsconfig. La parte più importante è il file outDir campo.

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

Ma c'è un problema. Non possiamo usare /src perché Netlify non può accedere al di fuori della cartella lambda. Quindi è necessario creare un bundle.

npm install --save ncp

È un pacchetto che permette di copiare le directory.

Aggiungi fagotto script per pacchetto.json

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

Ora se si esegue npm run bundlesi può vedere che nella nuova creazione lambda/bundle abbiamo tutti i file da src/.

Aggiornare il percorso di importazione all'interno di lambda/graphql.ts

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

{...}

Si può ora aggiungere lambda/bundle dir a .gitignore

Distribuzione con Netlify

Dobbiamo dire a Netlify qual è il comando di compilazione e dove risiedono le nostre funzioni. Per farlo, creiamo netlify.toml file:

// netlify.toml
[build]
  comando = "npm run build:lambda"
  funzioni = "lambda/dist"

Come si può vedere, si tratta della stessa cartella definita come una cartella outDir campo in lambda/tsconfig.json

Ecco come dovrebbe apparire la struttura dell'applicazione (beh... una parte di essa ;))

applicazione
└────lambda
│ └────bundle
│ │ graphql.ts
│ tsconfig.json.ts
└────src
│ └───generato
│ │ │ graphql.ts
│ │ index.ts
│ │ model.ts
│ │ resolvers.ts
│ │ schemi.ts
│ server.ts
│
│ codegen.yml
│ graphql.schema.json
│ netlify.toml
│ nodemon.json
│ tsconfig.json

Aggiungi bundle:lambda script per pacchetto.json

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

Distribuire

Proviamo a distribuire la nostra applicazione tramite Netlify.

  1. Accedere al proprio account Netlify,
  2. Connettetevi con il vostro Github,
  3. Fare clic sul pulsante "Nuovo sito da Git",
  4. Selezionare un repo appropriato,
  5. Impostare il comando di compilazione npm run build:lambda,
  6. Aggiungere la variabile d'ambiente (MONGODB_URI),
  7. Distribuire...

E TAAADAAAA...

paginaNotFound.png

Questo perché l'endpoint, per impostazione predefinita, non è l'elemento http://page/ ma http://page/.netlify/functions/graphql.

senzaRedirect.png

Come risolvere il problema? È molto semplice. Basta creare _redirects con:

/ /.netlify/functions/graphql 200!

Distribuire di nuovo e controllare.

reindirizzamento.png

Spero che vi piaccia! Sentitevi liberi di migliorare e cambiare.

Per saperne di più:

Come non uccidere un progetto con cattive pratiche di codifica?

Sicurezza delle applicazioni web. Vulnerabilità target="_blank"

Sicurezza delle applicazioni web - Vulnerabilità XSS

Articoli correlati

Sviluppo di software

Costruire applicazioni web a prova di futuro: le intuizioni del team di esperti di The Codest

Scoprite come The Codest eccelle nella creazione di applicazioni web scalabili e interattive con tecnologie all'avanguardia, offrendo esperienze utente senza soluzione di continuità su tutte le piattaforme. Scoprite come la nostra esperienza favorisce la trasformazione digitale e il business...

IL CANCRO
Sviluppo di software

Le 10 principali aziende di sviluppo software con sede in Lettonia

Scoprite le migliori aziende di sviluppo software della Lettonia e le loro soluzioni innovative nel nostro ultimo articolo. Scoprite come questi leader tecnologici possono aiutarvi a migliorare la vostra attività.

thecodest
Soluzioni per aziende e scaleup

Essenziali di sviluppo software Java: Guida all'outsourcing di successo

Esplorate questa guida essenziale sullo sviluppo di software Java con successo outsourcing per migliorare l'efficienza, accedere alle competenze e guidare il successo del progetto con The Codest.

thecodest
Sviluppo di software

La guida definitiva all'outsourcing in Polonia

L'aumento di outsourcing in Polonia è guidato dai progressi economici, educativi e tecnologici, che favoriscono la crescita dell'IT e un clima favorevole alle imprese.

IlCodesto
Soluzioni per aziende e scaleup

Guida completa agli strumenti e alle tecniche di audit IT

Gli audit IT garantiscono sistemi sicuri, efficienti e conformi. Per saperne di più sulla loro importanza, leggete l'articolo completo.

The Codest
Jakub Jakubowicz CTO e cofondatore

Iscrivetevi alla nostra knowledge base e rimanete aggiornati sulle competenze del settore IT.

    Chi siamo

    The Codest - Società internazionale di sviluppo software con centri tecnologici in Polonia.

    Regno Unito - Sede centrale

    • Ufficio 303B, 182-184 High Street North E6 2JA
      Londra, Inghilterra

    Polonia - Poli tecnologici locali

    • Parco uffici Fabryczna, Aleja
      Pokoju 18, 31-564 Cracovia
    • Ambasciata del cervello, Konstruktorska
      11, 02-673 Varsavia, Polonia

      The Codest

    • Casa
    • Chi siamo
    • Servizi
    • Case Studies
    • Sapere come
    • Carriera
    • Dizionario

      Servizi

    • Consulenza
    • Sviluppo di software
    • Sviluppo backend
    • Sviluppo Frontend
    • Staff Augmentation
    • Sviluppatori backend
    • Ingegneri del cloud
    • Ingegneri dei dati
    • Altro
    • Ingegneri QA

      Risorse

    • Fatti e miti sulla collaborazione con un partner esterno per lo sviluppo di software
    • Dagli Stati Uniti all'Europa: Perché le startup americane decidono di trasferirsi in Europa
    • Confronto tra gli hub di sviluppo Tech Offshore: Tech Offshore Europa (Polonia), ASEAN (Filippine), Eurasia (Turchia)
    • Quali sono le principali sfide di CTO e CIO?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • Condizioni di utilizzo del sito web

    Copyright © 2025 di The Codest. Tutti i diritti riservati.

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