portaldacalheta.pt
  • Principal
  • Design De Marca
  • Nutrição
  • Processos Financeiros
  • Vizinhos
Tecnologia

Criação de uma API REST segura em Node.js



As interfaces de programação de aplicativos (APIs) estão em toda parte. Eles permitem que o software se comunique com outras partes do software - interno ou externo - de forma consistente, o que é um ingrediente chave na escalabilidade, sem mencionar a reutilização.

É bastante comum hoje em dia que os serviços online tenham APIs voltadas ao público. Isso permite que outros desenvolvedores integrem facilmente recursos como logins de mídia social, pagamentos com cartão de crédito e rastreamento de comportamento. o de fato o padrão que eles usam para isso é chamado de transferência de estado representacional (REST).



Embora uma grande variedade de plataformas e linguagens de programação possam ser usadas para a tarefa, por exemplo, ASP.NET Core , Laravel (PHP) , ou Garrafa (Python) —Neste tutorial, criaremos um back-end básico, mas seguro, da API REST usando a seguinte pilha:



  • Node.js, com o qual o leitor já deve estar familiarizado
  • Express, que simplifica muito a construção de tarefas comuns de servidor da web em Node.js e é padrão na construção de um back-end de API REST
  • Mongoose, que conectará nosso back-end a um banco de dados MongoDB

Os desenvolvedores que seguem este tutorial também devem estar familiarizados com o terminal (ou prompt de comando).



Observação: não cobriremos uma base de código de front-end aqui, mas o fato de que nosso back-end é escrito em JavaScript torna conveniente compartilhar código - modelos de objeto, por exemplo - em toda a pilha.

Anatomia de uma API REST

APIs REST são usados ​​para acessar e manipular dados usando um conjunto comum de operações sem estado. Essas operações são parte integrante do protocolo HTTP e representam a funcionalidade essencial de criar, ler, atualizar e excluir (CRUD), embora não de uma maneira limpa um para um:



  • POST (crie um recurso ou geralmente forneça dados)
  • GET (recuperar um índice de recursos ou um recurso individual)
  • PUT (criar ou substituir um recurso)
  • PATCH (atualizar / modificar um recurso)
  • DELETE (remover um recurso)

Usando essas operações HTTP e um nome de recurso como endereço, podemos construir uma API REST criando um terminal para cada operação. E ao implementar o padrão, teremos uma base estável e facilmente compreensível, permitindo-nos evoluir o código rapidamente e mantê-lo depois. Conforme mencionado anteriormente, a mesma base será usada para integrar recursos de terceiros, muitos dos quais também usam APIs REST, tornando essa integração mais rápida.

Por enquanto, vamos começar a criar nossa API REST segura usando Node.js!



Neste tutorial, criaremos uma API REST bastante comum (e muito prática) para um recurso chamado users.

Nosso recurso terá a seguinte estrutura básica:



  • id (um UUID gerado automaticamente)
  • firstName
  • lastName
  • email
  • password
  • permissionLevel (o que este usuário tem permissão para fazer?)

E criaremos as seguintes operações para esse recurso:

  • POST no nó de extremidade /users (criar um novo usuário)
  • GET no nó de extremidade /users (lista todos os usuários)
  • GET no nó de extremidade /users/:userId (obter um usuário específico)
  • PATCH no nó de extremidade /users/:userId (atualize os dados para um usuário específico)
  • DELETE no nó de extremidade /users/:userId (remover um usuário específico)

Também usaremos tokens da web JSON (JWTs) para tokens de acesso. Para isso, criaremos outro recurso denominado auth que irá esperar o e-mail e a senha de um usuário e, em troca, irá gerar o token usado para autenticação em certas operações. (Excelente artigo de Dejan Milosevic sobre JWT para aplicativos REST seguros em Java entra em mais detalhes sobre isso; Os princípios são os mesmos.)



Configuração do tutorial da API REST

Em primeiro lugar, certifique-se de que possui a versão mais recente do Node.js instalada. Para este artigo, estarei usando a versão 14.9.0; também pode funcionar em versões mais antigas.

Em seguida, certifique-se de que você MongoDB instalado. Não explicaremos os detalhes do Mongoose e do MongoDB que são usados ​​aqui, mas para fazer o básico funcionar, basta iniciar o servidor no modo interativo (ou seja, a partir da linha de comando como mongo) em vez de como um serviço. Isso ocorre porque, em um ponto neste tutorial, precisaremos interagir com o MongoDB diretamente, em vez de por meio de nosso código Node.js.



Nota: Com o MongoDB, não há necessidade de criar um banco de dados específico como pode haver em alguns cenários RDBMS. A primeira chamada de inserção de nosso código Node.js irá disparar sua criação automaticamente.

Este tutorial não contém todo o código necessário para um projeto de trabalho. Pretende-se, em vez disso, que você clone o repo complementar e simplesmente siga os destaques à medida que lê - mas também pode copiar arquivos e fragmentos específicos do repositório conforme necessário, se preferir.

Navegue até o resultado rest-api-tutorial/ pasta em seu terminal. Você verá que nosso projeto contém três pastas de módulo:

  • common (lidar com todos os serviços compartilhados e informações compartilhadas entre os módulos do usuário)
  • users (tudo sobre usuários)
  • auth (lidando com a geração de JWT e o fluxo de login)

Agora, execute npm install (ou yarn se você tiver.)

Parabéns, agora você tem todas as dependências e configurações necessárias para executar nosso back end simples da API REST.

Criando o Módulo de Usuário

Estaremos usando Mangusto , uma biblioteca de modelagem de dados de objeto (ODM) para MongoDB, para criar o modelo de usuário dentro do esquema de usuário.

Primeiro, precisamos criar o esquema Mongoose em /users/models/users.model.js:

const userSchema = new Schema({ firstName: String, lastName: String, email: String, password: String, permissionLevel: Number });

Depois de definir o esquema, podemos anexar facilmente o esquema ao modelo de usuário.

const userModel = mongoose.model('Users', userSchema);

Depois disso, podemos usar esse modelo para implementar todas as operações CRUD que desejamos em nossos terminais Express.

a indústria automobilística ainda não implementou a internet das coisas (iot).

Vamos começar com a operação 'criar usuário', definindo a rota em users/routes.config.js:

app.post('/users', [ UsersController.insert ]);

Isso é colocado em nosso aplicativo Express no principal index.js Arquivo. O UsersController objeto é importado de nosso controlador, onde hash a senha apropriadamente, definida em /users/controllers/users.controller.js:

exports.insert = (req, res) => { let salt = crypto.randomBytes(16).toString('base64'); let hash = crypto.createHmac('sha512',salt) .update(req.body.password) .digest('base64'); req.body.password = salt + '$' + hash; req.body.permissionLevel = 1; UserModel.createUser(req.body) .then((result) => { res.status(201).send({id: result._id}); }); };

Neste ponto, podemos testar nosso modelo Mongoose executando o servidor (npm start) e enviando um POST solicitação para /users com alguns dados JSON:

{ 'firstName' : 'Marcos', 'lastName' : 'Silva', 'email' : ' [email protected] ', 'password' : 's3cr3tp4sswo4rd' }

Existem várias ferramentas que você pode usar para isso. Insomnia (abordado abaixo) e Postman são ferramentas GUI populares e curl é uma escolha CLI comum. Você pode até mesmo usar JavaScript, por exemplo, a partir do console de ferramentas de desenvolvimento integrado do seu navegador:

fetch('http://localhost:3600/users', { method: 'POST', headers: { 'Content-type': 'application/json' }, body: JSON.stringify({ 'firstName': 'Marcos', 'lastName': 'Silva', 'email': ' [email protected] ', 'password': 's3cr3tp4sswo4rd' }) }) .then(function(response) { return response.json(); }) .then(function(data) { console.log('Request succeeded with JSON response', data); }) .catch(function(error) { console.log('Request failed', error); });

Neste ponto, o resultado de uma postagem válida será apenas o id do usuário criado: { 'id': '5b02c5c84817bf28049e58a3' }. Precisamos também adicionar o createUser método para o modelo em users/models/users.model.js:

exports.createUser = (userData) => { const user = new User(userData); return user.save(); };

Tudo pronto, agora precisamos ver se o usuário existe. Para isso, vamos implementar o recurso “get user by id” para o seguinte endpoint: users/:userId.

Primeiro, criamos uma rota em /users/routes/config.js:

app.get('/users/:userId', [ UsersController.getById ]);

Em seguida, criamos o controlador em /users/controllers/users.controller.js:

exports.getById = (req, res) => { UserModel.findById(req.params.userId).then((result) => { res.status(200).send(result); }); };

E, finalmente, adicione o findById método para o modelo em /users/models/users.model.js:

exports.findById = (id) => { return User.findById(id).then((result) => { result = result.toJSON(); delete result._id; delete result.__v; return result; }); };

A resposta será assim:

{ 'firstName': 'Marcos', 'lastName': 'Silva', 'email': ' [email protected] ', 'password': 'Y+XZEaR7J8xAQCc37nf1rw==$p8b5ykUx6xpC6k8MryDaRmXDxncLumU9mEVabyLdpotO66Qjh0igVOVerdqAh+CUQ4n/E0z48mp8SDTpX2ivuQ==', 'permissionLevel': 1, 'id': '5b02c5c84817bf28049e58a3' }

Observe que podemos ver a senha com hash. Para este tutorial, estamos mostrando a senha, mas a melhor prática óbvia é nunca revelar a senha, mesmo que ela tenha sido hash. Outra coisa que podemos ver é o permissionLevel, que usaremos para lidar com as permissões do usuário mais tarde.

Repetindo o padrão apresentado acima, podemos agora adicionar a funcionalidade para atualizar o usuário. Usaremos o PATCH operação, pois nos permitirá enviar apenas os campos que queremos alterar. A rota será, portanto, PATCH para /users/:userid, e enviaremos todos os campos que desejamos alterar. Também precisaremos implementar alguma validação extra, uma vez que as alterações devem ser restritas ao usuário em questão ou a um administrador, e apenas um administrador deve ser capaz de alterar o permissionLevel. Vamos pular isso por agora e voltar a ele assim que implementarmos o módulo de autenticação. Por enquanto, nosso controlador será assim:

exports.patchById = (req, res) => { if (req.body.password){ let salt = crypto.randomBytes(16).toString('base64'); let hash = crypto.createHmac('sha512', salt).update(req.body.password).digest('base64'); req.body.password = salt + '$' + hash; } UserModel.patchUser(req.params.userId, req.body).then((result) => { res.status(204).send({}); }); };

Por padrão, enviaremos um código HTTP 204 sem corpo de resposta para indicar que a solicitação foi bem-sucedida.

E precisaremos adicionar o patchUser método para o modelo:

exports.patchUser = (id, userData) => { return User.findOneAndUpdate({ _id: id }, userData); };

A lista de usuários será implementada como GET em /users/ pelo seguinte controlador:

exports.list = (req, res) => { let limit = req.query.limit && req.query.limit { res.status(200).send(result); }) };

O método do modelo correspondente será:

exports.list = (perPage, page) => { return new Promise((resolve, reject) => { User.find() .limit(perPage) .skip(perPage * page) .exec(function (err, users) { if (err) { reject(err); } else { resolve(users); } }) }); };

A resposta da lista resultante terá a seguinte estrutura:

[ { 'firstName': 'Marco', 'lastName': 'Silva', 'email': ' [email protected] ', 'password': 'z4tS/DtiH+0Gb4J6QN1K3w==$al6sGxKBKqxRQkDmhnhQpEB6+DQgDRH2qr47BZcqLm4/fphZ7+a9U+HhxsNaSnGB2l05Oem/BLIOkbtOuw1tXA==', 'permissionLevel': 1, 'id': '5b02c5c84817bf28049e58a3' }, { 'firstName': 'Paulo', 'lastName': 'Silva', 'email': ' [email protected] ', 'password': 'wTsqO1kHuVisfDIcgl5YmQ==$cw7RntNrNBNw3MO2qLbx959xDvvrDu4xjpYfYgYMxRVDcxUUEgulTlNSBJjiDtJ1C85YimkMlYruU59rx2zbCw==', 'permissionLevel': 1, 'id': '5b02d038b653603d1ca69729' } ]

E a última parte a ser implementada é o DELETE em /users/:userId.

Nosso controlador para exclusão será:

exports.removeById = (req, res) => { UserModel.removeById(req.params.userId) .then((result)=>{ res.status(204).send({}); }); };

Da mesma forma que antes, o controlador retornará o código HTTP 204 e nenhum corpo de conteúdo como confirmação.

O método do modelo correspondente deve ser semelhante a este:

exports.removeById = (userId) => { return new Promise((resolve, reject) => { User.deleteMany({_id: userId}, (err) => { if (err) { reject(err); } else { resolve(err); } }); }); };

Agora temos todas as operações necessárias para manipular o recurso do usuário e terminamos com o controlador de usuário. A ideia principal deste código é fornecer os principais conceitos de uso do padrão REST. Precisamos retornar a este código para implementar algumas validações e permissões para ele, mas primeiro, precisaremos começar a construir nossa segurança. Vamos criar o módulo de autenticação.

Criando o Módulo Auth

Antes de podermos proteger o users ao implementar o middleware de permissão e validação, precisaremos ser capazes de gerar um token válido para o usuário atual. Geraremos um JWT em resposta ao usuário fornecer um e-mail e uma senha válidos. JWT é um notável token da web JSON que você pode usar para que o usuário faça várias solicitações com segurança, sem validar repetidamente. Geralmente, ele tem um tempo de expiração e um novo token é recriado a cada poucos minutos para manter a comunicação segura. Para este tutorial, entretanto, deixaremos de atualizar o token e mantê-lo simples com um único token por login.

Primeiro, criaremos um ponto de extremidade para POST solicitações para /auth recurso. O corpo da solicitação conterá o e-mail e a senha do usuário:

{ 'email' : ' [email protected] ', 'password' : 's3cr3tp4sswo4rd2' }

Antes de envolver o controlador, devemos validar o usuário em /authorization/middlewares/verify.user.middleware.js:

exports.isPasswordAndUserMatch = (req, res, next) => { UserModel.findByEmail(req.body.email) .then((user)=>{ if(!user[0]){ res.status(404).send({}); }else{ let passwordFields = user[0].password.split('$'); let salt = passwordFields[0]; let hash = crypto.createHmac('sha512', salt).update(req.body.password).digest('base64'); if (hash === passwordFields[1]) { req.body = { userId: user[0]._id, email: user[0].email, permissionLevel: user[0].permissionLevel, provider: 'email', name: user[0].firstName + ' ' + user[0].lastName, }; return next(); } else { return res.status(400).send({errors: ['Invalid email or password']}); } } }); };

Feito isso, podemos passar para o controlador e gerar o JWT:

exports.login = (req, res) => { try { let refreshId = req.body.userId + jwtSecret; let salt = crypto.randomBytes(16).toString('base64'); let hash = crypto.createHmac('sha512', salt).update(refreshId).digest('base64'); req.body.refreshKey = salt; let token = jwt.sign(req.body, jwtSecret); let b = Buffer.from(hash); let refresh_token = b.toString('base64'); res.status(201).send({accessToken: token, refreshToken: refresh_token}); } catch (err) { res.status(500).send({errors: err}); } };

Mesmo que não atualizemos o token neste tutorial, o controlador foi configurado para permitir tal geração para tornar mais fácil implementá-lo no desenvolvimento subsequente.

Tudo o que precisamos agora é criar a rota e invocar o middleware apropriado em /authorization/routes.config.js:

app.post('/auth', [ VerifyUserMiddleware.hasAuthValidFields, VerifyUserMiddleware.isPasswordAndUserMatch, AuthorizationController.login ]);

A resposta conterá o JWT gerado no campo accessToken:

{ 'accessToken': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI1YjAyYzVjODQ4MTdiZjI4MDQ5ZTU4YTMiLCJlbWFpbCI6Im1hcmNvcy5oZW5yaXF1ZUB0b3B0YWwuY29tIiwicGVybWlzc2lvbkxldmVsIjoxLCJwcm92aWRlciI6ImVtYWlsIiwibmFtZSI6Ik1hcmNvIFNpbHZhIiwicmVmcmVzaF9rZXkiOiJiclhZUHFsbUlBcE1PakZIRG1FeENRPT0iLCJpYXQiOjE1MjY5MjMzMDl9.mmNg-i44VQlUEWP3YIAYXVO-74803v1mu-y9QPUQ5VY', 'refreshToken': 'U3BDQXBWS3kyaHNDaGJNanlJTlFkSXhLMmFHMzA2NzRsUy9Sd2J0YVNDTmUva0pIQ0NwbTJqOU5YZHgxeE12NXVlOUhnMzBWMGNyWmdOTUhSaTdyOGc9PQ==' }

Tendo criado o token, podemos usá-lo dentro de Authorization cabeçalho usando o formulário Bearer ACCESS_TOKEN.

Criação de permissões e middleware de validação

A primeira coisa que devemos definir é quem pode usar o users recurso. Estes são os cenários com os quais precisamos lidar:

  • Público para criação de usuários (processo de registro). Não usaremos o JWT para este cenário.
  • Privado para o usuário conectado e para que os administradores atualizem esse usuário.
  • Privado para administrador apenas para remoção de contas de usuário.

Tendo identificado esses cenários, primeiro exigiremos um middleware que sempre valide o usuário se ele estiver usando um JWT válido. O middleware em /common/middlewares/auth.validation.middleware.js pode ser tão simples quanto:

exports.validJWTNeeded = (req, res, next) => { if (req.headers['authorization']) { try { let authorization = req.headers['authorization'].split(' '); if (authorization[0] !== 'Bearer') { return res.status(401).send(); } else { req.jwt = jwt.verify(authorization[1], secret); return next(); } } catch (err) { return res.status(403).send(); } } else { return res.status(401).send(); } };

Usaremos códigos de erro HTTP para lidar com erros de solicitação:

  • HTTP 401 para um pedido inválido
  • HTTP 403 para uma solicitação válida com um token inválido ou token válido com permissões inválidas

Podemos usar o operador AND bit a bit (bitmasking) para controlar as permissões. Se definirmos cada permissão necessária como uma potência de 2, podemos tratar cada bit do inteiro de 32 bits como uma única permissão. Um administrador pode então ter todas as permissões definindo seu valor de permissão para 2147483647. Esse usuário pode então ter acesso a qualquer rota. Como outro exemplo, um usuário cujo valor de permissão foi definido como 7 teria permissões para as funções marcadas com bits para os valores 1, 2 e 4 (dois elevado a 0, 1 e 2).

O middleware para isso seria assim:

cada parte do conteúdo abordado em aula será em um teste de unidade.
exports.minimumPermissionLevelRequired = (required_permission_level) => { return (req, res, next) => { let user_permission_level = parseInt(req.jwt.permission_level); let user_id = req.jwt.user_id; if (user_permission_level & required_permission_level) { return next(); } else { return res.status(403).send(); } }; };

O middleware é genérico. Se o nível de permissão do usuário e o nível de permissão necessário coincidirem em pelo menos um bit, o resultado será maior que zero e podemos deixar a ação prosseguir; caso contrário, o código HTTP 403 será retornado.

Agora, precisamos adicionar o middleware de autenticação às rotas do módulo do usuário em /users/routes.config.js:

app.post('/users', [ UsersController.insert ]); app.get('/users', [ ValidationMiddleware.validJWTNeeded, PermissionMiddleware.minimumPermissionLevelRequired(PAID), UsersController.list ]); app.get('/users/:userId', [ ValidationMiddleware.validJWTNeeded, PermissionMiddleware.minimumPermissionLevelRequired(FREE), PermissionMiddleware.onlySameUserOrAdminCanDoThisAction, UsersController.getById ]); app.patch('/users/:userId', [ ValidationMiddleware.validJWTNeeded, PermissionMiddleware.minimumPermissionLevelRequired(FREE), PermissionMiddleware.onlySameUserOrAdminCanDoThisAction, UsersController.patchById ]); app.delete('/users/:userId', [ ValidationMiddleware.validJWTNeeded, PermissionMiddleware.minimumPermissionLevelRequired(ADMIN), UsersController.removeById ]);

Isso conclui o desenvolvimento básico de nossa API REST. Tudo o que resta a fazer é testar tudo.

Executando e testando com insônia

Insônia é um cliente REST decente com uma boa versão gratuita. A melhor prática é, obviamente, incluir testes de código e implementar relatórios de erros adequados no projeto, mas clientes REST de terceiros são ótimos para testar e implementar soluções de terceiros quando o relatório de erros e depuração do serviço não estão disponíveis. Vamos usá-lo aqui para desempenhar o papel de um aplicativo e obter alguns insights sobre o que está acontecendo com nossa API.

Para criar um usuário, só precisamos POST os campos obrigatórios para o terminal apropriado e armazenar o ID gerado para uso subsequente.

Solicitar com os dados apropriados para criar um usuário

A API responderá com o ID do usuário:

Resposta de confirmação com ID do usuário

Agora podemos gerar o JWT usando /auth/ ponto final:

Solicitar com dados de login

Devemos obter um token como nossa resposta:

Confirmação contendo o JSON Web Token correspondente

Pegue o accessToken, prefixe-o com Bearer (lembre-se do espaço) e adicione-o aos cabeçalhos da solicitação em Authorization:

Configurar os cabeçalhos para transferência contém o JWT de autenticação

Se não fizermos isso agora que implementamos o middleware de permissões, todas as solicitações, exceto o registro, retornarão o código HTTP 401. Com o token válido, porém, obtemos a seguinte resposta de /users/:userId:

Resposta listando os dados do usuário indicado

Além disso, como foi mencionado antes, estamos exibindo todos os campos, para fins educacionais e por uma questão de simplicidade. A senha (com hash ou não) nunca deve ser visível na resposta.

Vamos tentar obter uma lista de usuários:

Solicitação de uma lista de todos os usuários

Surpresa! Recebemos uma resposta 403.

Ação recusada devido à falta de nível de permissão apropriado

Nosso usuário não tem permissão para acessar este endpoint. Teremos de alterar o permissionLevel de nosso usuário de 1 a 7 (ou mesmo 5 serviriam, já que nossos níveis de permissões gratuitas e pagas são representados como 1 e 4, respectivamente). Podemos fazer isso manualmente no MongoDB, em seu prompt interativo, como este (com o ID alterado para o seu resultado local):

db.users.update({'_id' : ObjectId('5b02c5c84817bf28049e58a3')},{$set:{'permissionLevel':5}})

Então, precisamos gerar um novo JWT.

Depois de fazer isso, obtemos a resposta adequada:

Resposta com todos os usuários e seus dados

A seguir, vamos testar a funcionalidade de atualização enviando um PATCH solicitação com alguns campos para nosso /users/:userId ponto final:

Pedido contendo dados parciais a serem atualizados

Esperamos uma resposta 204 como confirmação de uma operação bem-sucedida, mas podemos solicitar ao usuário uma nova verificação.

Resposta após mudança bem sucedida

Finalmente, precisamos deletar o usuário. Precisamos criar um novo usuário conforme descrito acima (não se esqueça de anotar o ID do usuário) e ter certeza de que temos o JWT apropriado para um usuário administrador. O novo usuário precisará de suas permissões definidas para 2053 (isto é, 2048— ADMIN —mais nosso 5 anterior) para poder realizar a operação de exclusão. Feito isso e um novo JWT gerado, teremos que atualizar nosso Authorization cabeçalho do pedido:

Solicitar configuração para exclusão de usuário

Enviando um DELETE solicitação para /users/:userId, devemos obter uma resposta 204 como confirmação. Podemos, novamente, verificar solicitando /users/ para listar todos os usuários existentes.

Próximas etapas para sua API REST

Com as ferramentas e métodos abordados neste tutorial, agora você deve ser capaz de criar APIs REST simples e seguras em Node.js. Muitas práticas recomendadas que não são essenciais para o processo foram ignoradas, então não se esqueça de:

  • Implementar validações adequadas (por exemplo, certifique-se de que o e-mail do usuário seja único)
  • Implementar testes de unidade e relatórios de erros
  • Impedir que os usuários alterem seu próprio nível de permissão
  • Impedir que os administradores removam a si próprios
  • Evite a divulgação de informações confidenciais (por exemplo, senhas com hash)
  • Mova o segredo JWT de common/config/env.config.js para um off-repo, não baseado no ambiente mecanismo de distribuição secreta

Um exercício final para o leitor pode ser converter a base de código de seu uso de promessas de JavaScript para o assíncrono / esperar técnica.

Para aqueles de vocês que possam estar interessados, agora há também uma versão TypeScript do projeto disponível.

Relacionado: 5 coisas que você nunca fez com uma especificação REST

Tutorial do Google Apps Script para dominar macros

Processos Financeiros

Tutorial do Google Apps Script para dominar macros
Tutorial do React: componentes, ganchos e desempenho

Tutorial do React: componentes, ganchos e desempenho

Tecnologia

Publicações Populares
Uma licença de design não é a resposta
Uma licença de design não é a resposta
Como o Prêmio Nobel da Paz revelou a cisão na oposição da Rússia
Como o Prêmio Nobel da Paz revelou a cisão na oposição da Rússia
Analista de produto, marketing de crescimento
Analista de produto, marketing de crescimento
Como dar feedback sobre design profissional
Como dar feedback sobre design profissional
UI x UX - Explore as principais diferenças (infográfico)
UI x UX - Explore as principais diferenças (infográfico)
 
Selecionando o método de avaliação correto para startups pré-IPO
Selecionando o método de avaliação correto para startups pré-IPO
The Ultimate UX Hook - Design Antecipatório, Persuasivo e Emocional na UX
The Ultimate UX Hook - Design Antecipatório, Persuasivo e Emocional na UX
O guia completo para métodos de pesquisa UX
O guia completo para métodos de pesquisa UX
Como criar fontes personalizadas: 7 etapas e 3 estudos de caso
Como criar fontes personalizadas: 7 etapas e 3 estudos de caso
Teste de solicitação HTTP: uma ferramenta de sobrevivência do desenvolvedor
Teste de solicitação HTTP: uma ferramenta de sobrevivência do desenvolvedor
Publicações Populares
  • como Warren Buffett investe
  • Cabeçalho c ++ e arquivos de origem
  • aprenda rápido ou objetivo c
  • Qual das alternativas a seguir não é um princípio de fechamento?
  • exemplo de autenticação baseada em token de segurança spring boot
  • o que é arte e design
Categorias
  • Design De Marca
  • Nutrição
  • Processos Financeiros
  • Vizinhos
  • © 2022 | Todos Os Direitos Reservados

    portaldacalheta.pt