Implantar a API GraphQL/MongoDB usando as Funções Netlify
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
Configurar os servidores locais e lambda.
Ligar ambos ao MongoDB.
Implementar a autenticação básica.
Implementar o Apollo sem servidor GraphQL API com Netlify.
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.
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.
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:
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);
});
};
{...}
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.
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.
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!
Veja o seu aspeto na base de dados.
Fixe! Tudo funciona bem!
Vamos tentar obter algumas informações sobre o utilizador.
E adicionar o campo da palavra-passe...
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.
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
OK, funciona bem! Tentar iniciar sessão
Vamos tentar novamente obter o utilizador, mas desta vez com o token adicionado aos cabeçalhos
Fixe! E se definirmos credenciais erradas?
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.