---
name: azion-sirva-uma-api-rest-de-uma-function-apoiada-no-sql-database
description: >-
  Implante uma function que serve todos os endpoints de uma API REST, lê o SQL Database por uma réplica e guarda a resposta de lista em cache.
---

# Sirva uma API REST de uma function apoiada no SQL Database

Você implanta, com a Azion CLI, uma function que serve todos os endpoints de uma API REST sob `/api/tasks`: ela lê as linhas do SQL Database por uma read replica, grava-as pela Azion API e mantém a resposta de lista em cache por 60 segundos. Para adicionar gravações a uma function que você já executa, consulte [Grave linhas no SQL Database a partir de uma function](/pt-br/documentacao/guias/desenvolvimento-de-aplicacoes/dados/gravar-linhas-no-sql-database-a-partir-de-uma-function/).

## Pré-requisitos

- SQL Database habilitado na sua conta e a permissão **Edit SQL Database**. O produto está em Preview, então solicite acesso pelo [Technical Support](/pt-br/documentacao/suporte/).
- Um banco de dados chamado `tasks-api`, o seu identificador e a tabela `tasks` com duas linhas. O identificador é o `id` que a resposta de criação retorna. Para criá-los, consulte [Crie um banco de dados usando a API](/pt-br/documentacao/guias/desenvolvimento-de-aplicacoes/dados/gerenciar-bancos-dados-edge-sql/#crie-um-banco-de-dados-usando-a-api) e [Crie tabelas e consulte dados](/pt-br/documentacao/guias/desenvolvimento-de-aplicacoes/dados/criar-tabelas-edge-sql/).
- O identificador do banco de dados e um personal token armazenados como as variáveis de ambiente `TASKS_DB_ID` e `TASKS_SQL_TOKEN`, como [Armazene o identificador do banco de dados e o token](/pt-br/documentacao/guias/desenvolvimento-de-aplicacoes/dados/gravar-linhas-no-sql-database-a-partir-de-uma-function/#armazene-o-identificador-do-banco-de-dados-e-o-token) mostra. Uma variável só chega à function depois de um deploy, então crie as duas antes do deploy.
- A Azion CLI instalada, com o seu personal token salvo. Para configurá-la, consulte [Primeiros passos com a Azion CLI](/pt-br/documentacao/devtools/cli/primeiros-passos/).
- Node.js e um gerenciador de pacotes, que a CLI usa para construir o projeto.

A function lê uma tabela criada com `CREATE TABLE IF NOT EXISTS tasks (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, completed INTEGER NOT NULL DEFAULT 0);`, com as linhas que `INSERT INTO tasks (title, completed) VALUES ('Write the API spec', 0);` e `INSERT INTO tasks (title, completed) VALUES ('Deploy the API', 1);` adicionam. `completed` é uma coluna inteira, `0` ou `1`, porque a function a lê como número. Os exemplos chamam a API em `api.example.com`. Substitua esse domínio pelo que o deploy exibe, e os nomes pelos seus.

---

## Escreva a function da API

A function é um handler ES Modules que implementa todos os endpoints sob `/api/tasks`. Ela lê com a classe `Database` do global `Azion.Sql`, envia cada gravação ao endpoint de query da Azion API com `TASKS_DB_ID` e `TASKS_SQL_TOKEN` e guarda a resposta de lista no cache `tasks-api` sob `max-age=60`. Todo `POST` e todo `DELETE` apaga essa lista guardada depois da gravação.

Para criar o projeto e adicionar a function:

1. **Crie o projeto**

   Execute `azion init`, insira `tasks-api` como nome e selecione o preset *Javascript* e o template *Hello World*, como [Faça o deploy de uma function com a Azion CLI](/pt-br/documentacao/guias/desenvolvimento-de-aplicacoes/functions-e-runtime/deploy-function-with-cli/) mostra. Depois, vá para o diretório do projeto.

2. **Substitua o handler**

   Substitua o conteúdo de `index.js`, o entrypoint que o build lê, pelo código abaixo.

```javascript
const { Database } = Azion.Sql;

const DATABASE_NAME = 'tasks-api';
const CACHE_NAME = 'tasks-api';
const LIST_MAX_AGE = 60;

function json(body, status = 200, headers = {}) {
  return new Response(JSON.stringify(body), {
    status,
    headers: { 'content-type': 'application/json', ...headers },
  });
}

// Reads come from a read replica. Integer parameters bind; string parameters are refused.
async function readTasks(id) {
  const connection = await Database.open(DATABASE_NAME);
  const rows = id === undefined
    ? await connection.query('SELECT id, title, completed FROM tasks ORDER BY id')
    : await connection.query('SELECT id, title, completed FROM tasks WHERE id = ?', [id]);
  const tasks = [];
  let row = await rows.next();
  while (row) {
    tasks.push({ id: row.getValue(0), title: row.getValue(1), completed: row.getValue(2) === 1 });
    row = await rows.next();
  }
  return tasks;
}

// Writes go to the main instance through the Azion API. A failed statement still answers 200.
async function writeTasks(statement) {
  const response = await fetch(
    `https://api.azion.com/v4/workspace/sql/databases/${Azion.env.get('TASKS_DB_ID')}/query`,
    {
      method: 'POST',
      headers: {
        Accept: 'application/json',
        Authorization: `Token ${Azion.env.get('TASKS_SQL_TOKEN')}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ statements: [statement] }),
    },
  );
  if (!response.ok) {
    throw new Error(`Azion API answered ${response.status}`);
  }
  const entry = (await response.json()).data[0];
  if (entry.error) {
    throw new Error(entry.error);
  }
  return entry.results;
}

// The query endpoint takes SQL strings, so a text value is quoted and its quotes doubled.
function sqlText(value) {
  return `'${String(value).replaceAll("'", "''")}'`;
}

export default {
  async fetch(request) {
    const url = new URL(request.url);
    const match = url.pathname.match(/^\/api\/tasks(?:\/(\d+))?$/);
    if (!match) {
      return json({ error: 'Not found' }, 404);
    }
    const id = match[1] === undefined ? undefined : Number(match[1]);
    const cache = await caches.open(CACHE_NAME);
    const listKey = `${url.origin}/api/tasks`;

    try {
      if (request.method === 'GET' && id === undefined) {
        const cached = await cache.match(listKey);
        if (cached) {
          return cached;
        }
        const list = json(await readTasks(), 200, {
          'cache-control': `max-age=${LIST_MAX_AGE}`,
          'x-tasks-cached-at': new Date().toISOString(),
        });
        await cache.put(listKey, list.clone());
        return list;
      }
      if (request.method === 'GET') {
        const [task] = await readTasks(id);
        return task ? json(task) : json({ error: 'Task not found' }, 404);
      }
      if (request.method === 'POST' && id === undefined) {
        const { title, completed = false } = await request.json();
        if (typeof title !== 'string' || title.length === 0) {
          return json({ error: 'title is required' }, 400);
        }
        const results = await writeTasks(
          `INSERT INTO tasks (title, completed) VALUES (${sqlText(title)}, ${completed ? 1 : 0}) RETURNING id`,
        );
        await cache.delete(listKey);
        return json({ id: results.rows[0][0], title, completed: Boolean(completed) }, 201);
      }
      if (request.method === 'DELETE' && id !== undefined) {
        const results = await writeTasks(`DELETE FROM tasks WHERE id = ${id}`);
        await cache.delete(listKey);
        return results.rows_written > 0
          ? json({ message: 'Task deleted' })
          : json({ error: 'Task not found' }, 404);
      }
      return json({ error: 'Method not allowed' }, 405);
    } catch (error) {
      console.log(error.message);
      return json({ error: 'Internal error' }, 500);
    }
  },
};
```

O `index.js` do projeto agora tem a function da API. Um caminho fora de `/api/tasks` responde `404`, um método que a function não trata responde `405`, e uma gravação que falha responde `500` e registra a mensagem dela no log.

O caso de uso [Criar APIs REST e GraphQL](/pt-br/documentacao/casos-de-uso/construir-e-executar-aplicacoes/criar-apis-rest-e-graphql/) usa os valores deste exemplo.

---

## Implante a API

Uma function serve todos os endpoints, então um deploy publica todos juntos. Para implantar a API, execute `azion deploy` no diretório do projeto. O comando constrói o projeto, cria a aplicação e a function, instancia a function e exibe o domínio que a serve. Para a saída do comando, consulte [Faça o deploy do projeto](/pt-br/documentacao/guias/desenvolvimento-de-aplicacoes/functions-e-runtime/deploy-function-with-cli/#5-faca-o-deploy-do-projeto).

A API responde no domínio que o deploy exibiu, todos os endpoints rodam na function, e a resposta de lista fica em cache por 60 segundos. O primeiro deploy pode levar vários minutos para responder em todas as localidades.

O caso de uso [Criar APIs REST e GraphQL](/pt-br/documentacao/casos-de-uso/construir-e-executar-aplicacoes/criar-apis-rest-e-graphql/) usa os valores deste exemplo.

---

## Confirme que a API responde

Cada verificação chama a API no domínio dela. Um primeiro deploy que ainda não responde ainda está se propagando; espere alguns minutos e tente de novo.

- **O endpoint de lista lê o banco de dados.** Requisite a lista:

  ```bash
  curl -i https://api.example.com/api/tasks
  ```

  A resposta traz `200` e as duas linhas que a tabela guarda:

  ```json
  [{"id":1,"title":"Write the API spec","completed":false},{"id":2,"title":"Deploy the API","completed":true}]
  ```

- **A lista vem da cópia em cache.** Repita a requisição em até 60 segundos. O header `x-tasks-cached-at` traz o mesmo valor da primeira resposta.

- **Uma gravação chega ao banco de dados.** Crie uma tarefa:

  ```bash
  curl -i -X POST https://api.example.com/api/tasks \
    -H "Content-Type: application/json" \
    -d '{"title": "Review the logs"}'
  ```

  A resposta traz `201` e o ID que o banco de dados atribuiu:

  ```json
  {"id":3,"title":"Review the logs","completed":false}
  ```

  Um `500` aqui significa que a gravação falhou. Leia a mensagem que a function registrou no log, como [Consultar logs de console de uma function](/pt-br/documentacao/guias/plataforma/observabilidade/consultar-function-console-events/) mostra: `Azion API answered 401` aponta para o valor de `TASKS_SQL_TOKEN`, e uma mensagem de erro do banco de dados aponta para o statement.

- **Uma gravação atualiza a lista.** Requisite a lista de novo. A resposta traz um novo valor de `x-tasks-cached-at` e três tarefas.

- **Uma tarefa inexistente responde 404.** Apague a mesma tarefa duas vezes:

  ```bash
  curl -X DELETE https://api.example.com/api/tasks/3
  ```

  A primeira chamada responde `{"message":"Task deleted"}`, e a segunda responde `404` com `{"error":"Task not found"}`.

A API lê, grava e apaga tarefas, e serve a lista pela cópia em cache entre uma gravação e outra.

Estas verificações confirmam o caso de uso [Criar APIs REST e GraphQL](/pt-br/documentacao/casos-de-uso/construir-e-executar-aplicacoes/criar-apis-rest-e-graphql/).

---

## Próximos passos

- [Armazene em cache a resposta de uma function com a Cache API](/pt-br/documentacao/guias/desenvolvimento-de-aplicacoes/functions-e-runtime/armazenar-em-cache-a-resposta-de-uma-function-com-a-cache-api.md): Como a chave, o max-age e a exclusão depois de uma gravação mantêm a resposta de lista atualizada.
- [Criar APIs REST e GraphQL](/pt-br/documentacao/casos-de-uso/construir-e-executar-aplicacoes/criar-apis-rest-e-graphql.md): O design que esta function implementa, com as medições que mostram que ele funciona.
