window.pipedriveLeadboosterConfig = { base: 'leadbooster-chat.pipedrive.com', companyId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', versión: 2, } ;(function () { var w = window if (w.LeadBooster) { console.warn('LeadBooster ya existe') } 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 }) }, } } })() Desplegar GraphQL/MongoDB API utilizando Netlify Functions - The Codest
The Codest
  • Quiénes somos
  • Servicios
    • Desarrollo de software
      • Desarrollo Frontend
      • Desarrollo backend
    • Staff Augmentation
      • Desarrolladores frontales
      • Desarrolladores de backend
      • Ingenieros de datos
      • Ingenieros de la nube
      • Ingenieros de control de calidad
      • Otros
    • Asesoramiento
      • Auditoría y consultoría
  • Industrias
    • Fintech y Banca
    • E-commerce
    • Adtech
    • Tecnología sanitaria
    • Fabricación
    • Logística
    • Automoción
    • IOT
  • Valor para
    • CEO
    • CTO
    • Gestor de entregas
  • Nuestro equipo
  • Case Studies
  • Saber cómo
    • Blog
    • Meetups
    • Seminarios en línea
    • Recursos
Carreras profesionales Póngase en contacto
  • Quiénes somos
  • Servicios
    • Desarrollo de software
      • Desarrollo Frontend
      • Desarrollo backend
    • Staff Augmentation
      • Desarrolladores frontales
      • Desarrolladores de backend
      • Ingenieros de datos
      • Ingenieros de la nube
      • Ingenieros de control de calidad
      • Otros
    • Asesoramiento
      • Auditoría y consultoría
  • Valor para
    • CEO
    • CTO
    • Gestor de entregas
  • Nuestro equipo
  • Case Studies
  • Saber cómo
    • Blog
    • Meetups
    • Seminarios en línea
    • Recursos
Carreras profesionales Póngase en contacto
Flecha atrás VOLVER
2021-05-13
Desarrollo de software

Despliegue GraphQL/MongoDB API usando Netlify Functions

The Codest

Pawel Rybczynski

Software Engineer

Objetivos Configuración inicial Instalar dependencias Empecemos Primero, añade el tsconfig.json al directorio principal: Ahora, vamos a crear src/server.ts para las implementaciones de los servidores. Luego agrega dos funciones: una para el servidor local y la segunda para lambda. Bien, no tenemos resolvers ni definiciones de tipos así que necesitamos crear algunos. Supongamos que, al principio, queremos [...]

Objetivos

  1. Configure tanto los servidores locales como los lambda.
  2. Conecta ambos a MongoDB.
  3. Implantar la autenticación básica.
  4. Despliegue de Apollo sin servidor GraphQL API con Netlify.
  5. Utilizar Typescript.

Configuración inicial

npm init -y

Instalar dependencias

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

Empecemos

En primer lugar, añada el tsconfig.json al directorio principal:

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

Ahora, vamos a crear src/server.ts para la implementación de servidores. A continuación, agregue dos funciones: una para el servidor local y la segunda para lambda.

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


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

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

export { createLambdaServer, createLocalServer };

Bien, no tenemos resolvers ni definiciones de tipos, así que necesitamos crear algunos. Supongamos que, al principio, queremos crear usuarios y recibir información sobre ellos.

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

const userSchema = gql`
  type Usuario {
    id: ID
    email: ¡String!
    nombre: ¡Cadena!
  }

  type Consulta {
    usuario(id: ID!): ¡Usuario!
  }

  tipo Mutation {
    createUser(nombre: ¡Cadena!, email: ¡Cadena!, contraseña: ¡Cadena!): ¡Usuario!
  }
`;

Si no estás familiarizado con esto, Apollo ha preparado un tutorial muy bonito

Ahora vamos a crear una resolución de usuario con una consulta y una mutación.

// src/resolvers.ts
const userResolver = {
  Consulta: {
    user: async (parent, args, context, info) => {
      {...}
    },
  },
  Mutación: {
    createUser: async (parent, args, context, info) => {
      {...}
    },
  },
};

Pero no tenemos datos... Vamos a arreglarlo 😉

No olvide importar la definición de tipo y el resolver al servidor.

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

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

{...}

Conectar con MongoDB a través de mongoose

Ahora es un buen momento para crear una conexión con nuestra base de datos. En este caso particular, será MongoDB. Es gratis y fácil de mantener. Pero antes de eso, vamos a instalar dos dependencias más:

npm install --save mongoose dotenv

El primer paso es crear un Modelo de Usuario.

// src/model.ts
import mongoose, { Documento, Error, Esquema } from "mongoose";

export type Usuario = Documento & {
  _id: cadena
  email: string
  nombre: cadena
  contraseña: cadena,
};

borrar mongoose.connection.models["Usuario"];

const UserSchema: Schema = new Schema({
  email: {
    tipo: String,
    requerido: true
    único: true,
  },
  nombre: {
    tipo: Cadena,
    requerido: true
    minLongitud: 3,
    maxLength: 32,
  },
  contraseña: {
    tipo: Cadena,
    requerido: true,
  },
});

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

Contraseñas más seguras

La seguridad ante todo Ahora vamos a asegurar nuestras contraseñas mediante hashing.

npm install --save bcrypt @tipos/bcrypt

Ahora, implementa la seguridad de la contraseña dentro del callback pre-middleware. Las funciones pre-middleware se ejecutan una tras otra, cuando cada middleware llama a continuación. Para hacer las contraseñas seguras, estamos usando una técnica que genera una sal y un hash en llamadas a funciones separadas.

// src/model.ts
import bcrypt from "bcrypt";
{...}
const SALT_WORK_FACTOR: número = 10;

UserSchema.pre("guardar", 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(usuario.contraseña, salt, function (err, hash) {
      if (err) return next(err);
      usuario.contraseña = hash;
      next();
    });
  });
});
{...}

A continuación, añada el método comparePasswords a UserSchema:

// src/model.ts
{...}
UserSchema.methods.comparePasswords = function (
  contraseñaCandidata: string,
  cb: (err: Error | null, same: boolean | null) => void
) {
  const usuario = this as Usuario;
  bcrypt.compare(candidContraseña, usuario.contraseña, (err, isMatch) => {
    if (err) {
      return cb(err, null);
    }
    cb(null, isMatch);
  });
};
{...}

Y, por supuesto, modificar el tipo de usuario.

tipo comparePasswordFunction = (
  contraseñaCandidata: cadena,
  cb: (err: Error, isMatch: boolean) => void
) => void;

exportar tipo User = Document & {
  _id: cadena
  email: cadena
  nombre: cadena
  contraseña: cadena,
  comparePasswords: comparePasswordFunction,
};

Ahora podemos organizar una conexión entre servidores y bases de datos. MONGODB_URI es una variable de entorno que contiene una cadena de conexión necesaria para crear la conexión. Puedes obtenerla desde el panel de tu cluster después de iniciar sesión en tu cuenta de MongoDB atlas. Ponla dentro de .env

// .env
MONGODB_URI = ...;

Recuerde siempre añadir ese archivo a .gitignore. Genial. Ahora vamos a añadir una función que permite conectar con db.

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

El contexto es un objeto que comparten todos los resolvers. Para proporcionarlo, sólo tenemos que añadir una función de inicialización del contexto al constructor de ApolloServer. Vamos a hacerlo.

// src/server.ts
import { userModel } from "./modelos/usuario.modelo";
{...}
const createLambdaServer = async () =>
  new ApolloServerLambda({
    typeDefs,
    resolvers,
    introspección: true,
    playground: true,
    context: async () => {
      await connectToDatabase();

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

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

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

Como puede ver, también estamos pasando userModel a través de contexto. Ahora podemos actualizar el resolver:

// resolvers.ts
const userResolver = {
  Consulta: {
    user: async (_, { email, name, password }, { models: { userModel } }) => {
      const user = await userModel.findById({ _id: id }).exec();
      return usuario;
    },
  },
  Mutación: {
    createUser: async (_, { id }, { models: { userModel } }) => {
      const usuario = await userModel.create({ email, nombre, contraseña });
      return usuario;
    },
  },
};

¡Qué bonito! Ahora cree una instancia de servidor básica:

// src/index.ts
import { crearServidorLocal } from "./servidor";
require("dotenv").config();

const puerto = proceso.entorno.PUERTO || 4000;

const servidor = createLocalServer();

server.listen(port).then(({ url }) => {
  console.log(`Servidor ir ejecutándose en ${url}`);
});

Iniciar el servidor local con Nodemon

Por último, antes de la ejecución, añada nodemon.json

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

Añadir script a paquete.json

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

¡Y corre!

npm iniciar

Deberías obtener esa información dentro del terminal:

Servidor ir funcionando en http://localhost:4000/

En caso afirmativo, abra el http://localhost:4000/ dentro de su navegador.

Debería aparecer la zona de juegos GraphQL. ¡Vamos a crear un nuevo usuario!

crearUsuario.png

Echa un vistazo a cómo se ve en la base de datos.

base de datosJohnDoe.png

¡Genial! Todo funciona bien.

Intentemos obtener información del usuario.

userWithoutAuth.png

Y añadir el campo de contraseña...

userContraseña.png

Muy bien. Recibimos error No se puede consultar el campo "contraseña" en el tipo "Usuario".". Como puede comprobar, no hemos añadido este campo dentro de la definición del tipo de usuario. Está ahí a propósito. No debemos hacer posible la consulta de contraseñas u otros datos sensibles.

Otra cosa... Podemos obtener datos del usuario sin ninguna autenticación... no es una buena solución. Tenemos que arreglarlo.

Pero antes...

Configurar Codegen

Utilicemos el GraphQL código para obtener un tipo base compatible, basado en nuestro esquema.

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

Cree codegen.yml

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

Añadir codegen script para paquete.json

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

A continuación, cuando el servidor local se está ejecutando, ejecute el script:

npm run codegen

En caso de éxito, recibirá un mensaje:

  √ Analizar la configuración
  √ Generar salidas

Si recibes esa información, deberían aparecer dos archivos:

  • graphql.esquema.json en el directorio principal
  • graphql.ts en la ruta recién creada src/generado

Nos interesa más el segundo. Si lo abre, observará una bonita estructura de tipos.

Ahora podemos mejorar nuestros resolutores:

// 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();
      return usuario;
    },
  },
  Mutación: {
    createUser: async (
      _,
      { email, name, password },
      { modelos: { userModel } }
    ): Promise => {
      const user = await userModel.create({
        email,
        nombre
        contraseña,
      });
      return usuario;
    },
  },
};

Autenticación

A continuación, vamos a configurar una autenticación simple basada en token.

npm install --save jsonwebtoken @tipos/jsonwebtoken

Cree checkAuth para verificar si el token es válido. Añadiremos el resultado al contexto para poder acceder a él dentro de los resolvers.

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

Además, necesitamos una forma de crear dicho token. La mejor manera es implementar una consulta de inicio de sesión dentro de nuestro resolver.

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

const userResolver: Resolvers = {
  Consulta: {
    user: async (_, { id }, { models: { userModel }, auth }): Promise => {
      if (!auth) throw new AuthenticationError("No estás autenticado");

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

      if (!user) throw new AuthenticationError("Credenciales no válidas");

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

      if (!matchPasswords) lanza un nuevo AuthenticationError("Credenciales no válidas");

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

      return { token };
    },
  },
  Mutación: {
    createUser: async (
      _,
      { email, name, password },
      { modelos: { userModel } }
    ): Promise => {
      const user = await userModel.create({
        email,
        nombre
        contraseña,
      });
      return usuario;
    },
  },
};

También es necesario actualizar las definiciones de tipo de usuario por tipo de token y consulta de inicio de sesión.

tipo Token {
    token: String
  }

type Consulta {
    usuario(id: ID!): ¡Usuario!
    login(email: String!, password: String!): ¡Token!
  }

Intentemos ahora obtener el usuario sin token

noAuth.png

OK, ¡funciona bien! Intenta iniciar sesión

ficha.png

Intentemos de nuevo obtener el usuario, pero esta vez con el token añadido a las cabeceras

conToken.png

¡Genial! ¿Y si nos equivocamos de credenciales?

wrongEmail.png

¡Genial!

Preparar el despliegue

Último paso: ¡desplegar la api sin servidor con Netlify!

Crear carpeta lambda en el directorio principal y poner dos archivos dentro:

Primero contiene AWS handler. Crea una instancia de servidor ApolloServerLambda y
y luego exponer un manejador usando createHandler de esa instancia.

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

export const handler = async (
  evento: APIGatewayProxyEvent,
  contexto: Contexto
) => {
  const servidor = await crearServidorLambda(evento, contexto);

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

Puede leer más al respecto.

La segunda, es el tsconfig. La parte importante es el 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"]
}

Pero hay un problema. No podemos utilizar /src dir porque Netlify no puede llegar fuera de la carpeta lambda. Así que debemos agruparlo.

npm install --save ncp

Es un paquete que permite copiar directorios.

Añadir paquete script para paquete.json

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

Ahora bien, si ejecuta npm run bundlese puede ver que en los nuevos lambda/paquete dir tenemos todos los archivos de src/.

Actualizar la ruta de importación dentro de lambda/graphql.ts

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

{...}

Ahora puede añadir lambda/bundle dir a .gitignore

Despliegue con Netlify

Debemos decirle a Netlify cual es el comando de compilación y donde viven nuestras funciones. Para ello vamos a crear netlify.toml file:

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

Como se puede ver, es el mismo directorio que se define como un outDir campo en lambda/tsconfig.json

Así es como debería ser la estructura de tu aplicación (bueno... parte de ella ;))

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

Añadir paquete:lambda script para paquete.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",
  },

Despliegue

Intentemos desplegar nuestra aplicación a través de Netlify.

  1. Accede a tu cuenta Netlify,
  2. Conecta con tu Github,
  3. Haga clic en el botón "Nuevo sitio desde Git",
  4. Seleccione un repositorio adecuado,
  5. Establezca el comando de compilación npm run build:lambda,
  6. Añadir variable de entorno (MONGODB_URI),
  7. Despliega...

Y TAAADAAAA...

pageNotFound.png

Esto se debe a que el punto final no es por defecto el http://page/ pero http://page/.netlify/functions/graphql.

sinRedireccion.png

¿Cómo solucionarlo? Es muy sencillo. Basta con crear _redirects con:

¡/ /.netlify/functions/graphql 200!

Despliegue de nuevo y compruebe.

redirigir.png

Espero que te haya gustado. Siéntase libre de mejorar y cambiar.

Más información:

¿Cómo no matar un proyecto con malas prácticas de codificación?

Seguridad de las aplicaciones web. Vulnerabilidad Target="_blank

Seguridad de aplicaciones web - Vulnerabilidad XSS

Artículos relacionados

Desarrollo de software

Crear aplicaciones web preparadas para el futuro: ideas del equipo de expertos de The Codest

Descubra cómo The Codest destaca en la creación de aplicaciones web escalables e interactivas con tecnologías de vanguardia, ofreciendo experiencias de usuario fluidas en todas las plataformas. Descubra cómo nuestra experiencia impulsa la transformación...

EL MEJOR
Desarrollo de software

Las 10 mejores empresas de desarrollo de software de Letonia

Conozca las principales empresas de desarrollo de software de Letonia y sus innovadoras soluciones en nuestro último artículo. Descubra cómo estos líderes tecnológicos pueden ayudarle a mejorar su negocio.

thecodest
Soluciones para empresas y escalas

Fundamentos del desarrollo de software Java: Guía para externalizar con éxito

Explore esta guía esencial sobre el desarrollo de software Java outsourcing con éxito para mejorar la eficiencia, acceder a la experiencia e impulsar el éxito de los proyectos con The Codest.

thecodest
Desarrollo de software

La guía definitiva para subcontratar en Polonia

El auge de las outsourcing en Polonia está impulsado por los avances económicos, educativos y tecnológicos, que fomentan el crecimiento de las TI y un clima favorable a las empresas.

TheCodest
Soluciones para empresas y escalas

Guía completa de herramientas y técnicas de auditoría informática

Las auditorías informáticas garantizan sistemas seguros, eficientes y conformes. Obtenga más información sobre su importancia leyendo el artículo completo.

The Codest
Jakub Jakubowicz CTO y Cofundador

Suscríbase a nuestra base de conocimientos y manténgase al día de la experiencia del sector informático.

    Quiénes somos

    The Codest - Empresa internacional de desarrollo de software con centros tecnológicos en Polonia.

    Reino Unido - Sede central

    • Oficina 303B, 182-184 High Street North E6 2JA
      Londres, Inglaterra

    Polonia - Centros tecnológicos locales

    • Parque de oficinas Fabryczna, Aleja
      Pokoju 18, 31-564 Cracovia
    • Embajada del Cerebro, Konstruktorska
      11, 02-673 Varsovia, Polonia

      The Codest

    • Inicio
    • Quiénes somos
    • Servicios
    • Case Studies
    • Saber cómo
    • Carreras profesionales
    • Diccionario

      Servicios

    • Asesoramiento
    • Desarrollo de software
    • Desarrollo backend
    • Desarrollo Frontend
    • Staff Augmentation
    • Desarrolladores de backend
    • Ingenieros de la nube
    • Ingenieros de datos
    • Otros
    • Ingenieros de control de calidad

      Recursos

    • Hechos y mitos sobre la cooperación con un socio externo de desarrollo de software
    • De EE.UU. a Europa: ¿Por qué las startups estadounidenses deciden trasladarse a Europa?
    • Comparación de los polos de desarrollo de Tech Offshore: Tech Offshore Europa (Polonia), ASEAN (Filipinas), Eurasia (Turquía)
    • ¿Cuáles son los principales retos de los CTO y los CIO?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • Condiciones de uso del sitio web

    Copyright © 2025 por The Codest. Todos los derechos reservados.

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