The Codest
  • Sobre nós
  • Serviços
    • Desenvolvimento de software
      • Desenvolvimento de front-end
      • Desenvolvimento backend
    • Staff Augmentation
      • Programadores Frontend
      • Programadores de back-end
      • Engenheiros de dados
      • Engenheiros de nuvem
      • Engenheiros de GQ
      • Outros
    • Aconselhamento
      • Auditoria e consultoria
  • Indústrias
    • Fintech e Banca
    • E-commerce
    • Adtech
    • Tecnologia da saúde
    • Fabrico
    • Logística
    • Automóvel
    • IOT
  • Valor para
    • CEO
    • CTO
    • Gestor de entregas
  • A nossa equipa
  • Case Studies
  • Saber como
    • Blogue
    • Encontros
    • Webinars
    • Recursos
Carreiras Entrar em contacto
  • Sobre nós
  • Serviços
    • Desenvolvimento de software
      • Desenvolvimento de front-end
      • Desenvolvimento backend
    • Staff Augmentation
      • Programadores Frontend
      • Programadores de back-end
      • Engenheiros de dados
      • Engenheiros de nuvem
      • Engenheiros de GQ
      • Outros
    • Aconselhamento
      • Auditoria e consultoria
  • Valor para
    • CEO
    • CTO
    • Gestor de entregas
  • A nossa equipa
  • Case Studies
  • Saber como
    • Blogue
    • Encontros
    • Webinars
    • Recursos
Carreiras Entrar em contacto
Seta para trás VOLTAR
2021-05-13
Desenvolvimento de software

Implantar a API GraphQL/MongoDB usando as Funções Netlify

The Codest

Pawel Rybczynski

Software Engineer

Objectivos Configuração inicial Instalar dependências Vamos começar Primeiro, adicione o tsconfig.json ao diretório principal: Agora, vamos criar o src/server.ts para as implementações dos servidores. Em seguida, adicione duas funções: uma para o servidor local e a segunda para o lambda. OK, nós não temos nenhum resolvedor ou definição de tipo, então precisamos criar alguns. Vamos assumir que, no início, queremos [...]

Objectivos

  1. Configurar os servidores locais e lambda.
  2. Ligar ambos ao MongoDB.
  3. Implementar a autenticação básica.
  4. Implementar o Apollo sem servidor GraphQL API com Netlify.
  5. Utilizar Typescript.

Configuração inicial

npm init -y

Instalar dependências

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

Vamos começar

Primeiro, adicione o tsconfig.json para o diretório principal:

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

Agora, vamos criar src/servidor.ts para implementações de servidores. Em seguida, adicione duas funções: uma para o servidor local e outra para o lambda.

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


const createLambdaServer = () =>
  novo ApolloServerLambda({
    typeDefs,
    resolvers,
    introspeção: true,
    playground: true,
    },
  });

const createLocalServer = () =>
  novo ApolloServer({
    typeDefs,
    resolvers,
    introspeção: true,
    playground: true,
    },
  });

exportar { createLambdaServer, createLocalServer };

OK, não temos quaisquer resolvedores ou definições de tipos, por isso precisamos de criar alguns. Vamos assumir que, no início, queremos criar utilizadores e receber informações sobre eles.

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

const userSchema = gql`
  tipo Utilizador {
    id: ID!
    email: String!
    nome: String!
  }

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

  tipo Mutação {
    createUser(name: String!, email: String!, password: String!): User!
  }
`;

Se não estiveres familiarizado com isto, O Apollo preparou um tutorial muito bom

Agora vamos criar um resolvedor de utilizadores com uma consulta e uma mutação.

// src/resolvers.ts
const userResolver = {
  Query: {
    utilizador: async (parent, args, context, info) => {
      {...}
    },
  },
  Mutação: {
    createUser: async (parent, args, context, info) => {
      {...}
    },
  },
};

Mas não temos dados... Vamos corrigir isso 😉

Não se esqueça de importar a definição de tipo e o resolvedor para o servidor.

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

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

{...}

Ligar ao MongoDB através do mongoose

Agora é uma boa altura para criar uma ligação com a nossa base de dados. Neste caso específico, será o MongoDB. Ele é gratuito e fácil de manter. Mas antes disso, vamos instalar mais duas dependências:

npm install --save mongoose dotenv

O primeiro passo é criar um modelo de utilizador.

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

exportar tipo User = Document & {
  _id: string,
  email: string,
  nome: string,
  password: string,
};

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

const UserSchema: Schema = new Schema({
  email: {
    type: String,
    required: true,
    unique: true,
  },
  name: {
    type: String,
    obrigatório: true,
    minLength: 3,
    maxLength: 32,
  },
  palavra-passe: {
    type: String,
    obrigatório: true,
  },
});

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

Tornar as palavras-passe mais seguras

A segurança em primeiro lugar! Vamos agora proteger as nossas palavras-passe através do hashing.

npm install --save bcrypt @types/bcrypt

Agora, implemente a segurança da palavra-passe no retorno de chamada do pré-middleware. As funções de pré-middleware são executadas uma após a outra, quando cada middleware faz a chamada seguinte. Para tornar as palavras-passe seguras, estamos a utilizar uma técnica que gera um sal e um hash em chamadas de função separadas.

// src/model.ts
importar bcrypt from "bcrypt";
{...}
const SALT_WORK_FACTOR: number = 10;

UserSchema.pre("save", function (next) {
  const user = this as User;
  se (!this.isModified("password")) return next();

  bcrypt.genSalt(SALT_WORK_FACTOR, function (err, salt) {
    se (err) return next(err);

    bcrypt.hash(user.password, salt, function (err, hash) {
      se (err) return next(err);
      user.password = hash;
      next();
    });
  });
});
{...}

Em seguida, adicione o método comparePasswords ao 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) => {
    se (err) {
      return cb(err, null);
    }
    cb(null, isMatch);
  });
};
{...}

E, claro, modificar o Tipo de utilizador.

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

exportar tipo User = Document & {
  _id: string,
  email: string,
  nome: string,
  password: string,
  comparePasswords: comparePasswordFunction,
};

Agora podemos estabelecer uma ligação entre os servidores e as bases de dados. MONGODB_URI é uma variável de ambiente que contém uma string de conexão necessária para criar a conexão. Pode obtê-la no seu painel de cluster depois de iniciar sessão na sua conta MongoDB atlas. Coloque-a dentro de .env

// .env
MONGODB_URI = ...;

Lembre-se sempre de adicionar esse ficheiro ao .gitignore. Ótimo! Agora vamos adicionar uma função que permita a ligação com a base de dados.

// src/servidor.ts
importar mongoose, { Connection } de "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;
};
{...}

O contexto é um objeto que é partilhado por todos os resolvedores. Para o fornecer, só precisamos de adicionar uma função de inicialização de contexto ao construtor do ApolloServer. Vamos fazer isso.

// src/servidor.ts
importar { userModel } de "./models/user.model";
{...}
const createLambdaServer = async () =>
  novo ApolloServerLambda({
    typeDefs,
    resolvers,
    introspeção: true,
    playground: true,
    contexto: async () => {
      await connectToDatabase();

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

const createLocalServer = () =>
  novo ApolloServer({
    typeDefs,
    resolvers,
    introspeção: true,
    playground: true,
    contexto: async () => {
      await connectToDatabase();

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

Como se pode ver, também estamos a passar userModel através de contexto. Podemos agora atualizar o resolvedor:

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

Parece bom! Agora crie uma instância básica de servidor:

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

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

const server = createLocalServer();

server.listen(port).then(({ url }) => {
  console.log(`Servidor está em execução em ${url}`);
});

Iniciar o servidor local com o Nodemon

Última coisa antes de correr, adicionar nodemon.json

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

Adicionar script a package.json

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

E corre!

npm start

Deve obter essas informações no terminal:

O servidor está a funcionar em http://localhost:4000/

Se sim, abra o http://localhost:4000/ dentro do seu browser.

O playground do GraphQL deve aparecer. Vamos criar um novo utilizador!

createUser.png

Veja o seu aspeto na base de dados.

base de dadosJohnDoe.png

Fixe! Tudo funciona bem!

Vamos tentar obter algumas informações sobre o utilizador.

userWithoutAuth.png

E adicionar o campo da palavra-passe...

userPassword.png

Boa! Recebemos o erro Não é possível consultar o campo "password" no tipo "User".. Como pode verificar, não adicionámos este campo na definição do tipo de utilizador. Ele está lá de propósito. Não devemos permitir a consulta de uma palavra-passe ou de outros dados sensíveis.

Outra coisa... Podemos obter dados do utilizador sem qualquer autenticação... não é uma boa solução. Temos de a corrigir.

Mas antes...

Configurar o Codegen

Vamos usar o GraphQL código para obter um tipo de base compatível, com base no nosso esquema.

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

Criar codegen.yml

sobrescrever: verdadeiro
schema: "http://localhost:4000"
gera:
  ./src/generated/graphql.ts:
    plugins:
      - "typescript"
      - "typescript-resolvers"
  ./graphql.schema.json:
    plugins:
      - "introspeção"

Adicionar codegénio script para package.json

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

Em seguida, quando o servidor local estiver a funcionar, execute o script:

npm run codegen

Em caso de sucesso, receberá uma mensagem:

  √ Analisar a configuração
  √ Gerar saídas

Se receber essa informação, devem aparecer dois ficheiros:

  • graphql.schema.json no diretório principal
  • graphql.ts no caminho recém-criado src/gerado

Estamos mais interessados na segunda. Se o abrir, notará uma bela estrutura de tipos.

Agora podemos melhorar os nossos resolvedores:

// src/resolvers.ts
importar { Resolvers, Token, User } de "./generated/graphql";

const userResolver: Resolvers = {
  Query: {
    user: async (_, { id }, { models: { userModel }, auth }): Promise => {
      const user = await userModel.findById({ _id: id }).exec();
      retorna utilizador;
    },
  },
  Mutação: {
    createUser: async (
      _,
      { email, nome, senha },
      { models: { userModel } }
    ): Promise => {
      const user = await userModel.create({
        email,
        nome,
        password,
      });
      return user;
    },
  },
};

Autenticação

Em seguida, vamos configurar uma autenticação simples baseada em token.

npm install --save jsonwebtoken @types/jsonwebtoken

Criar checkAuth para verificar se o token é válido. Vamos adicionar o resultado ao contexto para podermos aceder-lhe dentro dos resolvedores.

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

Além disso, precisamos de uma forma de criar esse token. A melhor maneira é implementar uma consulta de login dentro do nosso resolvedor.

// resolvers.ts
importar { Resolvers, Token, User } de "./generated/graphql";

const userResolver: Resolvers = {
  Query: {
    user: async (_, { id }, { models: { userModel }, auth }): Promise => {
      if (!auth) throw new AuthenticationError("Você não está autenticado");

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

      se (!user) throw new AuthenticationError("Credenciais inválidas");

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

      se (!matchPasswords) lançar um novo AuthenticationError("Credenciais inválidas");

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

      return { token };
    },
  },
  Mutação: {
    createUser: async (
      _,
      { email, nome, senha },
      { models: { userModel } }
    ): Promise => {
      const user = await userModel.create({
        email,
        nome,
        password,
      });
      return user;
    },
  },
};

Também é necessário atualizar as definições de tipo de utilizador por tipo de Token e consulta de início de sessão.

tipo Token {
    token: String!
  }

tipo Query {
    utilizador(id: ID!): Utilizador!
    login(email: String!, password: String!): Token!
  }

Vamos agora tentar obter o utilizador sem o token

noAuth.png

OK, funciona bem! Tentar iniciar sessão

ficha.png

Vamos tentar novamente obter o utilizador, mas desta vez com o token adicionado aos cabeçalhos

comToken.png

Fixe! E se definirmos credenciais erradas?

wrongEmail.png

Muito bem!

Preparar para implantar

Última etapa: implantar a API sem servidor com o Netlify!

Criar pasta lambda no diretório principal e colocar dois ficheiros no seu interior:

Primeiro contém AWS handler. Cria uma instância do servidor ApolloServerLambda e
e, em seguida, expor um manipulador utilizando createHandler dessa instância.

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

export const handler = async (
  evento: 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);
  });
};

Pode ler mais sobre o assunto.

O segundo é o tsconfig. A parte importante é o outDir domínio.

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

Mas há um problema. Não podemos usar /src dir porque o Netlify não consegue aceder fora da pasta lambda. Por isso, temos de o empacotar.

npm install --save ncp

É um pacote que permite copiar diretórios.

Adicionar feixe script para package.json

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

Agora, se executar npm run bundle, pode ver-se que no recém-criado lambda/bundle dir temos todos os ficheiros de src/.

Atualizar o caminho de importação dentro de lambda/graphql.ts

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

{...}

Agora você pode adicionar o diretório lambda/bundle ao .gitignore

Implementar com Netlify

Temos de dizer ao Netlify qual é o comando de compilação e onde estão as nossas funções. Para o fazer, vamos criar netlify.toml file:

// netlify.toml
[build]
  comando = "npm run build:lambda"
  funções = "lambda/dist"

Como se pode ver, é o mesmo diretório definido como um outDir campo em lambda/tsconfig.json

É assim que deve ser a estrutura da sua aplicação (bem... parte dela ;))

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

Adicionar pacote:lambda script para package.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",
  },

Implantar

Vamos tentar implementar a nossa aplicação através do Netlify.

  1. Inicie sessão na sua conta Netlify,
  2. Ligar ao seu Github,
  3. Clique no botão "Novo sítio a partir do Git",
  4. Selecione um repositório adequado,
  5. Definir o comando de construção npm run build:lambda,
  6. Adicionar variável de ambiente (MONGODB_URI),
  7. Implantar...

E TAAADAAAA...

pageNotFound.png

Isto deve-se ao facto de o ponto final não ser, por defeito, o http://page/ mas http://page/.netlify/functions/graphql.

withoutRedirect.png

Como é que se resolve? É muito simples. Basta criar _redirects com:

/ /.netlify/functions/graphql 200!

Implemente novamente e verifique.

redirecionar.png

Espero que tenham gostado! Sintam-se à vontade para melhorar e alterar.

Ler mais:

Como não matar um projeto com más práticas de codificação?

Segurança das aplicações Web. Vulnerabilidade Target="_blank

Segurança das aplicações Web - vulnerabilidade XSS

Artigos relacionados

Ilustração de uma aplicação de cuidados de saúde para smartphone com um ícone de coração e um gráfico de saúde em ascensão, com o logótipo The Codest, representando soluções digitais de saúde e HealthTech.
Desenvolvimento de software

Softwares para o setor de saúde: Tipos, casos de uso

As ferramentas em que as organizações de cuidados de saúde confiam atualmente não se assemelham em nada às fichas de papel de há décadas atrás. O software de cuidados de saúde apoia agora os sistemas de saúde, os cuidados aos doentes e a prestação de cuidados de saúde modernos em...

OCODEST
Ilustração abstrata de um gráfico de barras em declínio com uma seta ascendente e uma moeda de ouro que simboliza a eficiência ou a poupança de custos. O logótipo The Codest aparece no canto superior esquerdo com o slogan "In Code We Trust" sobre um fundo cinzento claro
Desenvolvimento de software

Como dimensionar a sua equipa de desenvolvimento sem perder a qualidade do produto

Aumentar a sua equipa de desenvolvimento? Saiba como crescer sem sacrificar a qualidade do produto. Este guia cobre sinais de que é hora de escalar, estrutura da equipe, contratação, liderança e ferramentas - além de como o The Codest pode...

OCODEST
Desenvolvimento de software

Construir aplicações Web preparadas para o futuro: ideias da equipa de especialistas do The Codest

Descubra como o The Codest se destaca na criação de aplicações web escaláveis e interactivas com tecnologias de ponta, proporcionando experiências de utilizador perfeitas em todas as plataformas. Saiba como a nossa experiência impulsiona a transformação digital e o negócio...

OCODEST
Desenvolvimento de software

As 10 principais empresas de desenvolvimento de software sediadas na Letónia

Saiba mais sobre as principais empresas de desenvolvimento de software da Letónia e as suas soluções inovadoras no nosso último artigo. Descubra como estes líderes tecnológicos podem ajudar a elevar o seu negócio.

thecodest
Soluções para empresas e escalas

Fundamentos do desenvolvimento de software Java: Um Guia para Terceirizar com Sucesso

Explore este guia essencial sobre o desenvolvimento de software Java outsourcing com sucesso para aumentar a eficiência, aceder a conhecimentos especializados e impulsionar o sucesso do projeto com The Codest.

thecodest

Subscreva a nossa base de conhecimentos e mantenha-se atualizado sobre os conhecimentos do sector das TI.

    Sobre nós

    The Codest - Empresa internacional de desenvolvimento de software com centros tecnológicos na Polónia.

    Reino Unido - Sede

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

    Polónia - Pólos tecnológicos locais

    • Parque de escritórios Fabryczna, Aleja
      Pokoju 18, 31-564 Cracóvia
    • Embaixada do Cérebro, Konstruktorska
      11, 02-673 Varsóvia, Polónia

      The Codest

    • Início
    • Sobre nós
    • Serviços
    • Case Studies
    • Saber como
    • Carreiras
    • Dicionário

      Serviços

    • Aconselhamento
    • Desenvolvimento de software
    • Desenvolvimento backend
    • Desenvolvimento de front-end
    • Staff Augmentation
    • Programadores de back-end
    • Engenheiros de nuvem
    • Engenheiros de dados
    • Outros
    • Engenheiros de GQ

      Recursos

    • Factos e mitos sobre a cooperação com um parceiro externo de desenvolvimento de software
    • Dos EUA para a Europa: Porque é que as empresas americanas decidem mudar-se para a Europa?
    • Comparação dos centros de desenvolvimento da Tech Offshore: Tech Offshore Europa (Polónia), ASEAN (Filipinas), Eurásia (Turquia)
    • Quais são os principais desafios dos CTOs e dos CIOs?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • Website terms of use

    Direitos de autor © 2026 por The Codest. Todos os direitos reservados.

    pt_PTPortuguese
    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 cs_CZCzech pt_PTPortuguese