Entendendo o conceito de APIs

É bem comum iniciantes na área de programação se depararem com a sigla API (que vem do inglês Application Programming Interface). Neste artigo vamos discutir esse conceito, indo da teoria acadêmica à implementação prática.

O que são APIs?

Application Programming Interface, ou Interface de Programação de Aplicações, é um conjunto de regras, nomes, entradas e saídas que permite que um software utilize funcionalidades de outro software sem precisar conhecer todos os seus detalhes internos.

Uma boa forma de entender API é pensar no balcão de atendimento de uma biblioteca. O usuário não precisa entrar no depósito, organizar prateleiras ou mexer no sistema interno para usar o atendimento. Ele apenas precisa fazer pedidos (“quero cadastrar um livro”, “quero listar os livros disponíveis”, “quero emprestar este exemplar”), enquanto a API funcionaria como o balcão, recebendo as solicitações, acionando as regras internas e devolvendo respostas.

Historicamente, a ideia de separar a “interface” da “implementação” remonta aos primórdios da Engenharia de Software. Parnas (1972) introduziu o conceito de Information Hiding (Ocultação de Informação), argumentando que os módulos de um sistema devem revelar o mínimo possível sobre seu funcionamento interno para os outros módulos.

A API nasceu justamente dessa necessidade de criar um “contrato” claro de como um sistema pode ser consumido por outro, abstraindo a complexidade de como o trabalho é efetivamente realizado nos bastidores. Em sistemas distribuídos e aplicações web, essa separação tornou-se ainda mais importante, pois diferentes sistemas precisam se comunicar por contratos relativamente estáveis. Dessa forma, há uma redução do acoplamento entre partes de um sistema.

Quando a internet se popularizou, esse conceito evoluiu para sistemas distribuídos. Fielding (2000) definiu o estilo arquitetural REST (sigla, do inglês, Representational State Transfer), que se tornou a base para a maioria das APIs Web modernas, permitindo que sistemas heterogêneos (um aplicativo móvel e um servidor em nuvem, por exemplo) troquem dados de forma padronizada.

Para conhecer mais sobre APIs RESTful especificamente, leia nosso outro artigo sobre o assunto: clique aqui

Para que a ideia de API foi criada?

O conceito de API foi criado para permitir comunicação controlada entre programas, bibliotecas, sistemas operacionais, serviços web ou módulos de uma aplicação. Sem APIs, cada parte do software precisaria conhecer diretamente a estrutura interna das outras partes, o que tornaria o sistema frágil e difícil de manter.

Na prática, APIs ajudam a resolver quatro problemas centrais: reutilização de funcionalidades, separação de responsabilidades, padronização de acesso e evolução do sistema sem quebrar tudo. Essa ideia dialoga com a modularização proposta por Parnas, pois uma API bem projetada permite trocar detalhes internos sem obrigar todos os consumidores da API a mudarem junto (Parnas, 1972).

Princípios fundamentais

Para que uma interface seja considerada uma API bem projetada, ela deve apresentar algumas propriedades inerentes. A primeira delas é ter contrato claro. Isso significa que ela precisa deixar evidente quais operações existem, quais dados devem ser enviados, quais respostas serão devolvidas e quais erros podem ocorrer. Em outras palavras, uma API estabelece entradas e saídas esperadas do tipo “se você enviar o dado X no formato Y, receberá o resultado Z“.

O segundo princípio é o encapsulamento (alguns chamam de abstração). Quem usa a API não deve depender diretamente da forma como os dados são guardados internamente ou da linguagem utilizada para construir o sistema.

Em terceiro, há o princípio da previsibilidade. Operações parecidas devem ter nomes, parâmetros e retornos parecidos. Num sistema de biblioteca, por exemplo, podem existir métodos com nomes como cadastrarLivro, listarLivros e emprestarLivro. Seguindo este padrão, a API fica mais fácil de se aprender porque segue um padrão mental coerente.

O quarto fundamento é a modularidade, que permite às APIs dividirem grandes sistemas monolíticos em ecossistemas de serviços menores e independentes (microsserviços), facilitando a manutenção e a evolução contínua.

Por fim, o quinto fundamento é o tratamento de erros de forma explícita. Em vez de falhar silenciosamente, ela deve informar quando um recurso não existe ou quando os dados obrigatórios do contrato não foram enviados pelo sistema solicitante.

Tipos de API: interna, de biblioteca e web (HTTP)

Devemos destacar que o termo API pode aparecer em diferentes contextos. A primeira possibilidade é uma API interna, que ocorre quando ela é usada dentro do próprio projeto, como fizemos com o código da bibliotecaApi. O segundo contexto ocorre quando importamos funcionalidades de ferramentas externas de uma biblioteca ou framework (express, sequelize etc.). Já uma API web (ou HTTP) permite comunicação entre sistemas pela rede mundial de computadores, geralmente usando o protocolo HTTP (obviamente).

Ressaltamos que essas categorias não são excludentes. Um sistema real pode ter uma API interna organizada em módulos, uma API web para comunicação com front-end e ainda consumir APIs externas, como serviços de pagamento, autenticação ou envio de e-mail.

Exemplo de API interna

Passada a teoria, vamos à aplicação do conteúdo. Neste exemplo simularemos o contexto de uma biblioteca para desenvolver os conceitos de uma API interna.

O primeiro passo é criar a pasta biblioteca-api (recomendamos que seja dentro da pasta Documentos, apesar de não ser obrigatório). Dentro de biblioteca-api, usaremos o esquema de diretórios conforme estabelecido abaixo:

biblioteca-api/
├── package.json
└── src/
├── dados/
│ └── livrosMemoria.js
├── servicos/
│ └── bibliotecaServico.js
├── api/
│ └── bibliotecaApi.js
└── index.js

Para iniciar o projeto em Javascript, digite no terminal do SO (confira se você está dentro da pasta biblioteca-api) o comando:

npm init -y

O comando criará o arquivo package.json. Abra-o e edite-o para incluir a configuração abaixo (faça a inclusão do código após a linha "version": "1.0.0"):

"type": "module",

O arquivo package.json deve ficar mais ou menos parecido com este:

{
  "name": "biblioteca-api",
  "version": "1.0.0",
  "type": "module",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

A propriedade "type": "module" informa ao Node.js que arquivos .js desse projeto devem ser tratados como módulos ESM. Assim, usarems import e export em vez de require e module.exports, que é o padrão mais moderno para JS.

Vamos começar criando uma pequena “base de dados” em memória. Para isso, vamos criar o arquivo livrosMemoria.js que simulará o “acervo interno” da biblioteca. No arquivo localizado em src/dados/livrosMemoria.js, inclua o código abaixo:

// src/dados/livrosMemoria.js

export const livrosMemoria = [
  {
    id: 1,
    titulo: "Dom Casmurro",
    autor: "Machado de Assis",
    emprestado: false
  },
  {
    id: 2,
    titulo: "O Cortiço",
    autor: "Aluísio Azevedo",
    emprestado: false
  }
];

Para cumprirmos o princípio do encapsulamento (uma API expõe operações, não detalhes internos), esse arquivo não deve ser usado diretamente pelo “usuário” da API. Quem consome a API não precisa saber se os livros estão em um array, em MySQL, PostgreSQL, MongoDB ou outro mecanismo.

Então, agora, vamos criar uma camada de serviço, onde ficam as regras de negócio. No arquivo localizado em src/servicos/bibliotecaServico.js, digite o código a seguir:

// src/servicos/bibliotecaServico.js

import { livrosMemoria } from "../dados/livrosMemoria.js";

export function cadastrarLivro(dadosDoLivro) {
  if (!dadosDoLivro.titulo || !dadosDoLivro.autor) {
    return {
      sucesso: false,
      mensagem: "Título e autor são obrigatórios."
    };
  }

  const novoLivro = {
    id: livrosMemoria.length + 1,
    titulo: dadosDoLivro.titulo,
    autor: dadosDoLivro.autor,
    emprestado: false
  };

  livrosMemoria.push(novoLivro);

  return {
    sucesso: true,
    mensagem: "Livro cadastrado com sucesso.",
    livro: novoLivro
  };
}

export function listarLivros() {
  return {
    sucesso: true,
    total: livrosMemoria.length,
    livros: livrosMemoria
  };
}

export function emprestarLivro(idDoLivro) {
  const livroEncontrado = livrosMemoria.find((livro) => livro.id === idDoLivro);

  if (!livroEncontrado) {
    return {
      sucesso: false,
      mensagem: "Livro não encontrado."
    };
  }

  if (livroEncontrado.emprestado) {
    return {
      sucesso: false,
      mensagem: "Livro já está emprestado."
    };
  }

  livroEncontrado.emprestado = true;

  return {
    sucesso: true,
    mensagem: "Livro emprestado com sucesso.",
    livro: livroEncontrado
  };
}

Neste ponto do nosso exemplo, já existe uma API interna do serviço, pois outros arquivos podem chamar os métodos cadastrarLivro, listarLivros e emprestarLivro. Porém, ainda podemos criar uma camada pública mais explícita para representar o contrato principal da aplicação.

Então, vamos criar a API pública da biblioteca. Ela vai importar as funções do serviço e expor apenas aquilo que queremos permitir. Localize o arquivo em src/api/bibliotecaApi.js e digite o código:

// src/api/bibliotecaApi.js

import {
  cadastrarLivro,
  listarLivros,
  emprestarLivro
} from "../servicos/bibliotecaServico.js";

export const bibliotecaApi = {
  cadastrarLivro,
  listarLivros,
  emprestarLivro
};

Esse arquivo funciona como uma “fachada” da biblioteca. O consumidor da API não precisa conhecer a pasta dados, nem acessar livrosMemoria, nem saber detalhes da busca com find. Ele usa apenas o contrato público: bibliotecaApi.cadastrarLivro, bibliotecaApi.listarLivros e bibliotecaApi.emprestarLivro. Esse desenho reforça o encapsulamento, mas também deixa claro o contrato da aplicação, ou seja, o que pode e o que não pode ser feito.

Seguindo com o exemplo, vamos consumir a API no arquivo principal. Nosso objetivo é devolver respostas previsíveis, atendendo o princípio da previsibilidade. Localize o arquivo em src/index.js e digite o código:

// src/index.js

import { bibliotecaApi } from "./api/bibliotecaApi.js";

console.log("=== Sistema da Biblioteca Comunitária ===");

const resultadoCadastro = bibliotecaApi.cadastrarLivro({
  titulo: "A Hora da Estrela",
  autor: "Clarice Lispector"
});

console.log("\nCadastro:");
console.log(resultadoCadastro.mensagem);
console.log(resultadoCadastro.livro);

const resultadoListagem = bibliotecaApi.listarLivros();

console.log("\nLivros cadastrados:");
for (const livro of resultadoListagem.livros) {
  console.log(`${livro.id} - ${livro.titulo}, de ${livro.autor}`);
}

const resultadoEmprestimo = bibliotecaApi.emprestarLivro(1);

console.log("\nEmpréstimo:");
console.log(resultadoEmprestimo.mensagem);
console.log(resultadoEmprestimo.livro);

const tentativaDuplicada = bibliotecaApi.emprestarLivro(1);

console.log("\nTentativa de novo empréstimo:");
console.log(tentativaDuplicada.mensagem);

Pronto! Exemplo de API interna básica finalizado. Vamos testar para descobrir se tudo está funcionando corretamente. No terminal, digite:

node src/index.js

Estamos acessando o arquivo index.js porque ele é o ponto de partida da nossa aplicação. Inclusive, se você observar o arquivo package.json (linha 6), vai encontrar essa definição. Ressaltamos que isso pode ser alterado, mas não o fizemos neste projeto.

A saída esperada será semelhante a esta:

=== Sistema da Biblioteca Comunitária ===

Cadastro:
Livro cadastrado com sucesso.
{ id: 3, titulo: 'A Hora da Estrela', autor: 'Clarice Lispector', emprestado: false }

Livros cadastrados:
1 - Dom Casmurro, de Machado de Assis
2 - O Cortiço, de Aluísio Azevedo
3 - A Hora da Estrela, de Clarice Lispector

Empréstimo:
Livro emprestado com sucesso.
{ id: 1, titulo: 'Dom Casmurro', autor: 'Machado de Assis', emprestado: true }

Tentativa de novo empréstimo:
Livro já está emprestado.

Observe que todas as operações devolvem objetos com estrutura semelhante: sucesso, mensagem e, quando necessário, livro ou livros. Isso torna a API previsível. Quem consome a API sabe onde procurar a mensagem e como verificar se a operação funcionou.

Além disso, observemos que os fundamentos de tratamento de erros e modularidade também foram atendidos neste projeto. O fato do index.js informar que o recurso (neste caso, o livro) não estava disponível, é uma informação clara de falha para o consumidor da API. E, por fim, como o sistema funciona em vários arquivos separados e organizados por função, temos uma modularidade que favorece a manutenção do sistema no longo prazo.

Exemplo de API web (HTTP)

Até aqui nosso projeto foi uma API interna, usada por módulos JavaScript. Agora vamos simular uma API web usando o módulo nativo http do Node.js. Este exemplo vai mostrar como a mesma lógica poderia ser acessada por requisições HTTP.

Crie o arquivo servidor.js dentro de src (caminho – src/servidor.js). Dentro do arquivo, digite o código abaixo:

// src/servidor.js

import http from "node:http";
import { bibliotecaApi } from "./api/bibliotecaApi.js";

const servidor = http.createServer((requisicao, resposta) => {
  resposta.setHeader("Content-Type", "application/json; charset=utf-8");

  if (requisicao.method === "GET" && requisicao.url === "/livros") {
    const resultado = bibliotecaApi.listarLivros();

    resposta.writeHead(200);
    resposta.end(JSON.stringify(resultado));
    return;
  }

  if (requisicao.method === "POST" && requisicao.url === "/livros") {
    let corpo = "";

    requisicao.on("data", (pedaco) => {
      corpo += pedaco;
    });

    requisicao.on("end", () => {
      const dadosDoLivro = JSON.parse(corpo);
      const resultado = bibliotecaApi.cadastrarLivro(dadosDoLivro);

      resposta.writeHead(resultado.sucesso ? 201 : 400);
      resposta.end(JSON.stringify(resultado));
    });

    return;
  }

  resposta.writeHead(404);
  resposta.end(
    JSON.stringify({
      sucesso: false,
      mensagem: "Rota não encontrada."
    })
  );
});

servidor.listen(3000, () => {
  console.log("API da biblioteca rodando em http://localhost:3000");
});

Agora, para rodar a API web, execute no terminal:

node src/servidor.js

Depois, abra outro terminal e teste a listagem dos livros:

curl http://localhost:3000/livros

E teste o cadastro (obs: o comando abaixo funcionará no Bash, mas não no Windows Powershell):

curl -X POST http://localhost:3000/livros \
  -H "Content-Type: application/json" \
  -d '{"titulo":"Capitães da Areia","autor":"Jorge Amado"}'

Nesse exemplo, a API passa a ser acessada por HTTP. O cliente não chama diretamente funções JavaScript; ele envia requisições para rotas como GET /livros e POST /livros. Ainda assim, a ideia central é a mesma: existe um contrato de comunicação entre consumidor e fornecedor.

Vantagens de usar APIs

Construir código usando APIs cria uma distinção clara entre uso e implementação, facilitando leitura, manutenção e testes. Isso nos dá uma boa organização e padronização para trabalhar. Além disso, equipes podem programar de forma independente. Por exemplo, a equipe de frontend pode construir a tela baseada no contrato da API enquanto a equipe de backend está finalizando a lógica de negócios.

Outra vantagem percebida é a reutilização de código. Conforme aponta Fowler (2002), o desenvolvimento focado em interfaces promove um alto grau de reúso. Uma mesma API de pagamentos pode ser consumida por um site web, um app Android/iOS e um sistema de totem de autoatendimento.

Finalmente, destacamos a possibilidade de evolução controlada do software. Se futuramente a forma de armazenamento dos dados ou a linguagem de construção for modificada, o consumidor da API pode continuar chamando o método – por exemplo, bibliotecaApi.listarLivros() – sem precisar saber que houve uma mudança.

Desvantagens de usar APIs

Porém, é importante lembrar que APIs também introduzem custo de projeto. Antes de “botar a mão na massa”, é preciso pensar em nomes, formatos de entrada, formatos de resposta, erros, versionamento e documentação. Ou seja, há todo um processo de planejamento antes para que uma API não se torne difícil de usar e de alterar.

Outro trade-off relevante é que uma API simplifica o acesso, mas pode esconder complexidades importantes do negócio. Caso o contrato seja simplificado demais, ele pode limitar usos futuros; em contrapartida, se for genérico demais, pode ficar confuso. Equilibrar essa modelagem exige experiência e conhecimento profundo sobre o domínio da aplicação.

Também existe o problema da compatibilidade, que ocorre quando uma API já está sendo usada por outros sistemas e qualquer mudança pode quebrar a comunicação com essas aplicações consumidoras. É por isso que APIs públicas exigem cuidado com versionamento, estabilidade e comunicação de mudanças.

Conclusão

APIs são interfaces de comunicação entre partes de software criadas para permitir que sistemas, módulos e aplicações interajam por meio de contratos claros, sem depender diretamente dos detalhes internos uns dos outros.

O ponto central de uma boa API é a clareza, a previsibilidade e a estabilidade que ela pode oferecer para facilitar a evolução de sistemas sem comprometer aplicações que dependam dela. No exemplo que demonstramos neste artigo, a API permitiu cadastrar livros, listar o acervo e realizar empréstimos sem expor a estrutura interna dos dados. Apesar de básica, nossa API emprega os fundamentos necessários para uma boa comunicação.

Obrigado pela leitura e bons estudos!

Referências

FIELDING, Roy Thomas. Architectural styles and the design of network-based software architectures. Tese (Doutorado em Ciência da Computação) – University of California, Irvine, 2000.

FOWLER, Martin. Patterns of Enterprise Application Architecture. Boston: Addison-Wesley, 2002.

PARNAS, David Lorge. On the criteria to be used in decomposing systems into modules. Communications of the ACM, New York, v. 15, n. 12, p. 1053-1058, 1972.