Contents

Como criar e consumir APIs de simulação em aplicativos React usando Mirage.js

Ao desenvolver aplicações de pilha completa, uma parte significativa do trabalho do frontend depende de dados em tempo real do backend.

A situação acima mencionada sugere que pode ser necessário adiar o desenvolvimento da interface do utilizador até que a interface de programação de aplicações (API) esteja preparada para utilização. No entanto, atrasar o início do desenvolvimento do front-end em antecipação à disponibilidade da API pode impedir significativamente o progresso e prolongar a duração do projeto.

Uma solução adequada para este problema pode ser conseguida através da utilização de APIs de simulação. Estas API permitem o desenvolvimento e o teste de um sistema front-end utilizando estruturas de dados simuladas, sem ter de recorrer à API autêntica.

Introdução às APIs simuladas do Mirage.js

O Mirage.js é uma biblioteca JavaScript que permite criar APIs simuladas, completas com um servidor de teste em execução no lado do cliente do seu aplicativo Web. Isto significa que pode testar o seu código de frontend sem ter de se preocupar com a disponibilidade ou comportamento da sua API de backend real.

/pt/images/laptop-with-code-on-the-screen.jpg

Para utilizar o Mirage.js, é necessário estabelecer um conjunto de endpoints de API simulados e especificar as respostas correspondentes que esses endpoints devem fornecer. Subsequentemente, o Mirage.js irá capturar todos os pedidos feitos pela sua aplicação front-end ao servidor Web e substituir as respostas simuladas no seu lugar.

Uma vez que a sua interface de programação de aplicações (API) tenha sido totalmente desenvolvida e testada, a sua implementação no seu projeto torna-se um processo simples que requer apenas a atualização das definições de configuração no Mirage.js. Esta abordagem simplificada permite uma integração perfeita com o mínimo de perturbação da funcionalidade ou do desempenho existentes.

É possível localizar o código-fonte deste projeto no repositório GitHub associado.

Criar um servidor de API simulado com o Mirage.js

Para estabelecer uma API simulada para um aplicativo React utilizando o Mirage.js como back-end, inicialmente construiremos um aplicativo de gerenciamento de tarefas simples em React. Antes de prosseguir com esse esforço, há várias etapas preliminares que devem ser realizadas. Em primeiro lugar, é preciso criar um novo aplicativo React empregando um dos dois métodos disponíveis. O método inicial envolve a execução da instrução de linha de comando create-react-app , que é uma abordagem convencional e amplamente reconhecida. Por outro lado, um caminho alternativo implica a criação de um projeto React através da implementação do Vite, um servidor de desenvolvimento moderno que ganhou destaque devido ao seu desempenho rápido. Uma vez feita a escolha entre estas opções, é imperativo introduzir as dependências necessárias, instalando o Mirage.js através do gestor de pacotes ou do registo npm.

 npm install --save-dev miragejs 

Para estabelecer uma instância do servidor Mirage.js que capta os pedidos recebidos e imita as respostas da API, utilize o método createServer que aceita um objeto de configuração que o acompanha.

A entidade acima mencionada inclui o contexto ambiental e a convenção de nomenclatura associada para a nossa interface de programação de aplicações (API). Especificamente, o parâmetro de ambiente denota a fase atual do ciclo de vida da API, tal como o seu estado de desenvolvimento, enquanto o espaço de nomes serve como uma designação prefacial anexada a cada ponto de extremidade da API, conferindo-lhe assim um identificador único.

De facto, vou criar um novo ficheiro src/server.js e incorporar nele o excerto de código fornecido. O script resultante irá lidar com pedidos de clientes que procuram informações sobre produtos ou serviços oferecidos pela sua empresa. Primeiro, ele analisará o pedido recebido para determinar seu tipo, se é para obter detalhes do produto ou informações sobre preços. Com base nesta determinação, extrai os dados relevantes da base de dados utilizando a sua chave primária como identificador. Se for bem-sucedido, o servidor enviará uma resposta contendo as informações solicitadas no formato JSON. No entanto, se o pedido falhar devido a parâmetros de entrada inválidos, será devolvida uma mensagem de erro.

 import { createServer, Model } from 'miragejs';

const DEFAULT_CONFIG = {
  environment: "development",
  namespace: "api",
};

export function makeServer({ environment, namespace } =
   DEFAULT_CONFIG) {
   let server = createServer({
      environment,
      namespace,
      models: {
      Todo: Model,
     },
  });

  return server;
}

Para alinhar o espaço de nomes com a estrutura específica do URL da sua API, tem a flexibilidade de o personalizar de acordo com os seus requisitos. Além disso, se necessário, pode incluir um número de versão no espaço de nomes para garantir a compatibilidade com futuras actualizações ou modificações da API. Ao fazê-lo, quando a sua API backend estiver totalmente desenvolvida e operacional, a sua incorporação na sua interface de utilizador exigirá apenas pequenos ajustes na sua base de código.

Além disso, é possível especificar um esquema de dados nas definições de configuração do servidor para imitar o processo de armazenamento e recuperação de informações num contexto simulado.

De facto, para iniciar o funcionamento do Mirage.js, é necessário importar o objeto servidor acima mencionado para o ficheiro index.jsx ou main.jsx. Isto pode ser conseguido através do seguinte processo:

 import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import { makeServer } from './server';

if ( process.env.NODE_ENV === 'development' &&
      typeof makeServer === 'function'
   ) {
  makeServer();}

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
) 

Adicionar dados de semente à API simulada

Utilizando o banco de dados de memória integrado do Mirage.js, os desenvolvedores têm a capacidade de pré-carregar sua API simulada com dados de semente iniciais para fins de teste, bem como gerenciar efetivamente os dados de teste gerados por aplicativos clientes. Esta funcionalidade permite o armazenamento e a recuperação contínuos de dados de teste na base de dados simulada, que podem depois ser utilizados na aplicação cliente.

Para incorporar dados iniciais na API simulada, deve incluir as seguintes linhas de código imediatamente após o objeto ‘models’ no ficheiro ‘server.js’.

 seeds(server) {
      server.create('Todo', {
        title: 'item no 1',
        body:
          'Do something nice for someone I care about',
      });
      server.create('Todo', {
        title: 'item no 2',
        body:
          'Memorize the fifty states and their capitals.',
      });
      server.create('Todo', {
        title: 'item no 3',
        body:
          'Watch a classic movie.',
      });
    }, 

A função seeds preenche um servidor Mirage.js com três itens de tarefas, cada um com um título e uma descrição. Opcionalmente, em vez de codificar os dados de teste, pode integrar uma biblioteca como a Faker.js para gerar os dados de teste necessários.

Definir as rotas da API simulada

Para implementar uma API simulada funcional, é necessário estabelecer vários pontos de extremidade da API que possam acomodar solicitações GET, POST e DELETE. Essas rotas servirão como a base da funcionalidade da nossa API simulada, permitindo-nos simular várias interações com o servidor e testar diferentes cenários. Ao definir essas rotas, podemos efetivamente emular um endpoint de API real e validar seu comportamento sob diferentes condições.

Abaixo do conjunto de dados inicial, incorpore o código fornecido da seguinte forma:

 routes() {
      this.namespace = 'api/todos';
       
      this.get('/', (schema, request) => {
        return schema.all('Todo');
       });

      this.post('/', (schema, request) => {
        let attrs = JSON.parse(request.requestBody);
        return schema.create('Todo', attrs);
       });

      this.delete('/:id', (schema, request) => {
        let id = request.params.id;
        return schema.find('Todo', id).destroy();
       });
    } 

Construir um cliente React

Uma vez que nossa API simulada tenha sido estabelecida, prosseguiremos com a construção de um cliente React que fará a interface com e utilizará os pontos de extremidade da API. Você tem permissão para empregar qualquer biblioteca de componentes de interface do usuário que preferir; no entanto, para fins de clareza, este tutorial usará o Chakra UI para enriquecer a aparência do aplicativo.

Primeiro, instale essas dependências:

 npm install @chakra-ui/react @emotion/react @emotion/styled framer-motion 

Na verdade, vamos gerar uma nova instância do arquivo TodoList.jsx, englobando o bloco de código subsequente:

 import React, { useState, useEffect } from 'react';
import {
  Button,
  Box,
  Container,
  Text,
  Input,
  FormControl,
  Flex,
} from '@chakra-ui/react'; 

Considerando a tarefa em questão, seria prudente desenvolver um componente funcional que englobe os elementos necessários para renderizar a interface de usuário da lista de tarefas, incluindo campos de entrada que facilitam a adição de novas tarefas, bem como uma enumeração das tarefas atuais.

 export default function TodoList() {
  return (
    <Container>
      <Text fontSize="xl" mb={4}>Todo List</Text>
      <FormControl mb={4}>
        <Input
          type="text"
          name="body"
          value={newTodo.body}
          onChange={handleInputChange}
        />
      </FormControl>
      <Button colorScheme="teal" onClick={handleAddTodo}> Add Todo</Button>
      {loading ? ( <Text>Loading...</Text> ) : (
        todos.map((todo) => (
          <Box key={todo.id} mb={2} p={2} borderWidth="1px">
            <Flex align="center">
              <Text flex="1">{todo.body}</Text>
              <Button
                colorScheme="red"
                size="sm"
                onClick={() => handleDelete(todo.id)}>Delete
               </Button>
            </Flex>
          </Box>
        ))
      )}
    </Container>
  );
} 

uma para adicionar um item à lista (ADD\_ITEM) e outra para remover um item da lista (REMOVE\_ITEM). Estas acções devem ser enviadas pelos respectivos manipuladores em resposta à entrada do utilizador ou a outros eventos relevantes. Para gerenciar o estado do aplicativo, utilizaremos o hook useReducer junto com a função redutora apropriada. Essa abordagem nos permite manter uma árvore de estado consistente em todos os componentes, mantendo nosso código limpo e organizado.

 const [todos, setTodos] = useState([]);
const [newTodo, setNewTodo] = useState({ title: '', body: '' });
const [loading, setLoading] = useState(true);
const [renderKey, setRenderKey] = useState(0); 

Ao inicializar a aplicação num browser da Web, é necessário implementar um mecanismo para recuperar e apresentar os dados da semente armazenados numa base de dados na memória. Para atingir esse objetivo, podemos utilizar o gancho useEffect fornecido pelo React, encapsulando a chamada da função fetch em seu escopo. Isso garantirá que os dados da semente sejam recuperados e exibidos após o carregamento do aplicativo.

  useEffect(() => {
    fetch('/api/todos')
       .then((response) => response.json())
      .then((data) => {
        setTodos(data.todos);
        setLoading(false);
      });
  }, [renderKey]); 

A inclusão do estado renderKey no âmbito do gancho useEffect serve para facilitar a reativação de qualquer informação recentemente incorporada presente na nossa base de dados local e operacional sempre que o sistema de alojamento estiver a funcionar corretamente.

Essencialmente, sempre que um utilizador introduzir novas informações de tarefas no repositório Mirage.js, o componente será submetido a um processo de regeneração para refletir visualmente os dados alterados.

Adição de dados à API

Para facilitar a adição de dados através de pedidos HTTP POST, é essencial implementar uma estrutura lógica na interface de programação de aplicações (API). Imediatamente após a utilização do gancho useEffect , incorpore a seguinte implementação.

 const handleInputChange = (e) => {
  const { name, value } = e.target;
  setNewTodo((prevTodo) => ({ ...prevTodo, [name]: value }));
};

const handleAddTodo = () => {
  setLoading(true);
  fetch('/api/todos', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(newTodo),
  }).then((response) => response.json()).then((createdTodo) => {
      setTodos((prevTodos) => [createdTodo, ...prevTodos]);
      setNewTodo({ title: '', body: '' });
      setRenderKey((prevKey) => prevKey \\+ 1);
       setLoading(false);
     }).catch((error) => {
      console.error('Error adding todo:', error);
      setLoading(false);
     });
}; 

Após a introdução de dados no campo de entrada designado e o clique no botão “Add Todo” pelo utilizador, o sistema actualiza o estado atual de “newTodo” com as informações fornecidas. Subsequentemente, um pedido HTTP POST simulado é enviado para a API contendo uma instância da estrutura de dados actualizada no corpo do pedido para armazenamento na base de dados da memória.

Após um pedido POST bem sucedido, o código actualiza processualmente a matriz “todos”, acrescentando-lhe o objeto de tarefa recém-criado. Posteriormente, pede ao componente funcional React para fazer uma reavaliação, apresentando assim visualmente o item de tarefa atualizado na lista da página.

Pedidos de DELETE da API simulada

Para excluir dados por meio de pedidos de DELETE da API simulada, é necessário estabelecer uma estrutura lógica para esse processo. Essencialmente, trata-se de transmitir um pedido DELETE com o objetivo de erradicar a tarefa em causa da base de dados temporária. Por conseguinte, se a operação for eficaz, é essencial modificar tanto a lista de tarefas pendentes como o indicador de carregamento para indicar que o procedimento de remoção foi executado com êxito.

 const handleDelete = (id) => {
     let deleteInProgress = true;
    fetch(`/api/todos/${id}`, {
      method: 'DELETE',
    }).then((response) => {
        if (response.status === 204) {
          return null;
        } else {
          return response.json();
        }
      }) .then((data) => {
        if (data && data.error) {
          console.error('Error deleting todo:', data.error);
        } else {
           setTodos((prevTodos) => prevTodos.filter((todo) => todo.id !== id));
          setRenderKey((prevKey) => prevKey \\+ 1);
        }
        deleteInProgress = false;
      }).catch((error) => {
        console.error('Error deleting todo:', error);
        deleteInProgress = false;
      }) .finally(() => {
        setLoading(deleteInProgress);
      });
  }; 

Tenha em atenção que este método é capaz de eliminar apenas os dados que foram recentemente adicionados e não o conjunto de dados inicial.

Incorporar o componente TodoList no ficheiro App.jsx , de modo a exibi-lo no Document Object Model (DOM).

 import TodoList from './components/TodoList';
//code ...
<TodoList />

De facto, ao iniciar o servidor de desenvolvimento, é possível recuperar o conjunto de dados inicial e modificar interactivamente a API simulada na sua aplicação React, adicionando e eliminando dados.

Usando APIs simuladas para acelerar o desenvolvimento

A utilização de APIs simuladas apresenta um método eficiente para avançar no desenvolvimento de front-end, tanto ao conduzir o trabalho de forma independente quanto em um ambiente colaborativo. Através do emprego de APIs simuladas, é possível construir rapidamente interfaces de utilizador e avaliar o código associado, sem ter de esperar pela conclusão do back-end.