

As traduções são geradas por tradução automática. Em caso de conflito entre o conteúdo da tradução e da versão original em inglês, a versão em inglês prevalecerá.

# Usando o Aurora PostgreSQL com a API de dados em AWS AppSync
<a name="aurora-serverless-tutorial-js"></a>

 

Saiba como conectar sua API GraphQL aos bancos de dados Aurora PostgreSQL usando o. AWS AppSync Essa integração permite que você crie aplicativos escaláveis e orientados por dados executando consultas e mutações SQL por meio de operações do GraphQL. AWS AppSync fornece uma fonte de dados para executar instruções SQL em clusters do Amazon Aurora que são habilitados com uma API de dados. Você pode usar AWS AppSync resolvedores para executar instruções SQL na API de dados com consultas, mutações e assinaturas do GraphQL.

Antes de começar este tutorial, você deve ter familiaridade básica com AWS serviços e conceitos do GraphQL.

**nota**  
Este tutorial usa a Região `US-EAST-1`. 

**Topics**
+ [Configurar o banco de dados Aurora PostgreSQL](#creating-clusters)
+ [Criar o banco de dados e uma tabela](#creating-db-table)
+ [Criar um esquema do GraphQL](#rds-graphql-schema)
+ [Resolvedores para RDS](#rds-resolvers)
+ [Excluir o cluster](#rds-delete-cluster)

## Configurar o banco de dados Aurora PostgreSQL
<a name="creating-clusters"></a>

Antes de adicionar uma fonte de dados do Amazon RDS AWS AppSync, faça o seguinte.

1. Ative uma API Data em um cluster do Aurora Serverless v2.

1. Configure um segredo usando AWS Secrets Manager

1. Crie o cluster usando o AWS CLI comando a seguir.

   ```
   aws rds create-db-cluster \
               --db-cluster-identifier appsync-tutorial \
               --engine aurora-postgresql \
               --engine-version 16.6 \
               --serverless-v2-scaling-configuration MinCapacity=0,MaxCapacity=1 \
               --master-username USERNAME \
               --master-user-password COMPLEX_PASSWORD \
               --enable-http-endpoint
   ```

Isso retornará um ARN para o cluster. Depois de criar um cluster, você deve adicionar uma instância Serverless v2 com o comando a seguir. AWS CLI 

```
aws rds create-db-instance \
    --db-cluster-identifier appsync-tutorial \
    --db-instance-identifier appsync-tutorial-instance-1 \
    --db-instance-class db.serverless \
    --engine aurora-postgresql
```

**nota**  
Estes endpoints levam tempo para serem ativados. Você pode consultar o status no console do RDS na guia **Conectividade e segurança** do cluster.

Verifique o status do cluster com o AWS CLI comando a seguir.

```
aws rds describe-db-clusters \
    --db-cluster-identifier appsync-tutorial \
    --query "DBClusters[0].Status"
```

Crie um segredo por meio do AWS Secrets Manager console ou AWS CLI com um arquivo de entrada, como o seguinte, usando o `USERNAME` e `COMPLEX_PASSWORD` da etapa anterior:

```
{
    "username": "USERNAME",
    "password": "COMPLEX_PASSWORD"
}
```

Passe isso como um parâmetro para AWS CLI:

```
aws secretsmanager create-secret \
    --name appsync-tutorial-rds-secret \
    --secret-string file://creds.json
```

Isso retornará um ARN para o segredo. **Anote** o ARN do seu cluster Aurora Serverless v2 e do Secret para mais tarde ao criar uma fonte de dados no console. AWS AppSync 

## Criar o banco de dados e uma tabela
<a name="creating-db-table"></a>

Primeiro, crie um banco de dados chamado `TESTDB`. No PostgreSQL, um banco de dados é um contêiner que contém tabelas e outros objetos SQL. Valide se o cluster do Aurora Serverless v2 está configurado corretamente antes de adicioná-lo à API do AWS AppSync . Primeiro, crie um banco de dados *TESTDB* com o parâmetro `--sql` da maneira a seguir.

```
aws rds-data execute-statement \
    --resource-arn "arn:aws:rds:us-east-1:111122223333 ISN:cluster:appsync-tutorial" \
    --secret-arn "arn:aws:secretsmanager:us-east-1:111122223333 ISN:secret:appsync-tutorial-rds-secret" \
    --sql "create DATABASE \"testdb\"" \
    --database "postgres"
```

 Se isso for executado sem erros, inclua duas tabelas com o comando `create table`:

```
 aws rds-data execute-statement \
    --resource-arn "arn:aws:rds:us-east-1:111122223333 ISN:cluster:appsync-tutorial" \
    --secret-arn "arn:aws:secretsmanager:us-east-1:111122223333 ISN:secret:appsync-tutorial-rds-secret" \
    --database "testdb" \
    --sql 'create table public.todos (id serial constraint todos_pk primary key, description text not null, due date not null, "createdAt" timestamp default now());'

aws rds-data execute-statement \
    --resource-arn "arn:aws:rds:us-east-1:111122223333 ISN:cluster:appsync-tutorial" \
    --secret-arn "arn:aws:secretsmanager:us-east-1:111122223333 ISN:secret:appsync-tutorial-rds-secret" \
    --database "testdb" \
    --sql 'create table public.tasks (id serial constraint tasks_pk primary key, description varchar, "todoId" integer not null constraint tasks_todos_id_fk references public.todos);'
```

Se bem-sucedido, adicione o cluster como uma fonte de dados na API.

## Criar um esquema do GraphQL
<a name="rds-graphql-schema"></a>

Agora que a API Data do Aurora Serverless v2 está em execução com tabelas configuradas, criaremos um esquema GraphQL. Você pode criar rapidamente a API importando configurações de tabela a partir de um banco de dados existente usando o assistente de criação de APIs.

Para começar: 

1. No AWS AppSync console, escolha **Create API** e, em seguida, **Start with an Amazon Aurora cluster**. 

1. Especifique os detalhes da API, como **Nome da API**, e selecione o banco de dados para gerar a API.

1. Selecione o banco de dados. Se necessário, atualize a região e selecione o cluster do Aurora e o banco de dados *TESTDB*. 

1. Selecione o segredo e escolha **Importar**. 

1. Depois que as tabelas forem descobertas, atualize os nomes dos tipos. Altere `Todos` para `Todo` e `Tasks` para `Task`. 

1. Visualize o esquema gerado selecionando **Visualizar esquema**. O esquema terá a seguinte aparência: 

   ```
   type Todo {
     id: Int!
     description: String!
     due: AWSDate!
     createdAt: String
   }
   
   type Task {
     id: Int!
     todoId: Int!
     description: String
   }
   ```

1. Para a função, você pode AWS AppSync criar uma nova função ou criar uma com uma política semelhante à abaixo:

------
#### [ JSON ]

****  

   ```
   {
       "Version":"2012-10-17",		 	 	 
       "Statement": [
           {
               "Effect": "Allow",
               "Action": [
                   "rds-data:ExecuteStatement"
               ],
               "Resource": [
                   "arn:aws:rds:us-east-1:111122223333:cluster:appsync-tutorial",
                   "arn:aws:rds:us-east-1:111122223333:cluster:appsync-tutorial:*"
               ]
           },
           {
               "Effect": "Allow",
               "Action": [
                   "secretsmanager:GetSecretValue"
               ],
               "Resource": [
               "arn:aws:secretsmanager:us-east-1:111122223333:secret:appsync-tutorial-rds-secret",
               "arn:aws:secretsmanager:us-east-1:111122223333:secret:appsync-tutorial-rds-secret:*"
               ]
           }
       ]
   }
   ```

------

   Observe que há duas declarações nesta política à qual você está concedendo acesso ao perfil. O primeiro recurso é o cluster do Aurora e o segundo é o ARN do AWS Secrets Manager . 

   Selecione **Próximo**, revise os detalhes da configuração e escolha **Criar API**. Agora você tem uma API totalmente operacional. É possível revisar os detalhes completos da API na página **Esquema**. 

## Resolvedores para RDS
<a name="rds-resolvers"></a>

O fluxo de criação da API criou automaticamente os resolvedores para interagir com nossos tipos. Se consultar a página **Esquema**, você encontrará alguns dos resolvedores a seguir.
+ Criar um `todo` por meio do campo `Mutation.createTodo`.
+ Atualizar um `todo` por meio do campo `Mutation.updateTodo`.
+ Excluir um `todo` por meio do campo `Mutation.deleteTodo`.
+ Obter um único `todo` por meio do campo `Query.getTodo`.
+ Listar todos os `todos` por meio do campo `Query.listTodos`.

Você encontrará campos e resolvedores semelhantes anexados para o tipo `Task`. Vamos examinar com mais cuidado alguns dos resolvedores. 

### Mutation.createTodo
<a name="createtodo"></a>

No editor de esquemas no AWS AppSync console, no lado direito, escolha `testdb` ao `createTodo(...): Todo` lado de. O código do resolvedor usa a função `insert` do módulo do `rds` para criar dinamicamente uma declaração de inserção que adiciona dados à tabela `todos`. Como estamos trabalhando com o Postgres, podemos aproveitar a declaração `returning` para recuperar os dados inseridos.

Atualize o resolvedor a seguir para especificar devidamente o tipo `DATE` do campo `due`.

```
import { util } from '@aws-appsync/utils';
import { insert, createPgStatement, toJsonObject, typeHint } from '@aws-appsync/utils/rds';

export function request(ctx) {
    const { input } = ctx.args;
    // if a due date is provided, cast is as `DATE`
    if (input.due) {
        input.due = typeHint.DATE(input.due)
    }
    const insertStatement = insert({
        table: 'todos',
        values: input,
        returning: '*',
    });
    return createPgStatement(insertStatement)
}

export function response(ctx) {
    const { error, result } = ctx;
    if (error) {
        return util.appendError(
            error.message,
            error.type,
            result
        )
    }
    return toJsonObject(result)[0][0]
}
```

Salve o resolvedor. A dica de tipo marca a propriedade `due` no objeto de entrada como um tipo `DATE`. Isso permite que o mecanismo Postgres interprete adequadamente o valor. Depois, atualize o esquema para remover o `id` da entrada `CreateTodo`. Como nosso banco de dados Postgres pode exibir o ID gerado, você pode contar com nele para criar e retornar o resultado como uma única solicitação da maneira a seguir.

```
input CreateTodoInput {
    due: AWSDate!
    createdAt: String
    description: String!
}
```

Faça a alteração e atualize o esquema. Vá até o editor **Consultas** para adicionar um item ao banco de dados da maneira a seguir.

```
mutation CreateTodo {
  createTodo(input: {description: "Hello World!", due: "2023-12-31"}) {
    id
    due
    description
    createdAt
  }
}
```

Você recebe o resultado a seguir.

```
{
  "data": {
    "createTodo": {
      "id": 1,
      "due": "2023-12-31",
      "description": "Hello World!",
      "createdAt": "2023-11-14 20:47:11.875428"
    }
  }
}
```

### Query.listTodos
<a name="listtodo"></a>

No editor de esquemas no console, à direita, selecione `testdb` ao lado de `listTodos(id: ID!): Todo`. O manipulador de solicitações usa a função de utilitário select para criar uma solicitação dinamicamente em runtime.

```
export function request(ctx) {
    const { filter = {}, limit = 100, nextToken } = ctx.args;
    const offset = nextToken ? +util.base64Decode(nextToken) : 0;
    const statement = select({
        table: 'todos',
        columns: '*',
        limit,
        offset,
        where: filter,
    });
    return createPgStatement(statement)
}
```

Queremos filtrar `todos` com base na data `due`. Vamos atualizar o resolvedor no qual converter valores `due` em `DATE`. Atualize a lista de importações e o manipulador de solicitações da maneira a seguir.

```
import { util } from '@aws-appsync/utils';
import * as rds from '@aws-appsync/utils/rds';

export function request(ctx) {
  const { filter: where = {}, limit = 100, nextToken } = ctx.args;
  const offset = nextToken ? +util.base64Decode(nextToken) : 0;

  // if `due` is used in a filter, CAST the values to DATE.
  if (where.due) {
    Object.entries(where.due).forEach(([k, v]) => {
      if (k === 'between') {
        where.due[k] = v.map((d) => rds.typeHint.DATE(d));
      } else {
        where.due[k] = rds.typeHint.DATE(v);
      }
    });
  }

  const statement = rds.select({
    table: 'todos',
    columns: '*',
    limit,
    offset,
    where,
  });
  return rds.createPgStatement(statement);
}

export function response(ctx) {
  const {
    args: { limit = 100, nextToken },
    error,
    result,
  } = ctx;
  if (error) {
    return util.appendError(error.message, error.type, result);
  }
  const offset = nextToken ? +util.base64Decode(nextToken) : 0;
  const items = rds.toJsonObject(result)[0];
  const endOfResults = items?.length < limit;
  const token = endOfResults ? null : util.base64Encode(`${offset + limit}`);
  return { items, nextToken: token };
}
```

No editor **Consultas**, faça o seguinte.

```
query LIST {
  listTodos(limit: 10, filter: {due: {between: ["2021-01-01", "2025-01-02"]}}) {
    items {
      id
      due
      description
    }
  }
}
```

### Mutation.updateTodo
<a name="updatetodo"></a>

Também é possível `update` um `Todo`. No editor de **consultas**, vamos atualizar o primeiro item `Todo` de `id` `1`.

```
mutation UPDATE {
  updateTodo(input: {id: 1, description: "edits"}) {
    description
    due
    id
  }
}
```

Observe que é preciso especificar o `id` do item que você está atualizando. Também é possível determinar uma condição para atualizar somente um item que atenda a condições específicas. Por exemplo, só convém editar o item caso a descrição comece com `edits` da maneira a seguir.

```
mutation UPDATE {
  updateTodo(input: {id: 1, description: "edits: make a change"}, condition: {description: {beginsWith: "edits"}}) {
    description
    due
    id
  }
}
```

Assim como processamos as operações `create` e `list`, podemos atualizar o resolvedor para converter o campo `due` em `DATE`. Salve essas alterações em `updateTodo` da amaneira a seguir.

```
import { util } from '@aws-appsync/utils';
import * as rds from '@aws-appsync/utils/rds';

export function request(ctx) {
  const { input: { id, ...values }, condition = {}, } = ctx.args;
  const where = { ...condition, id: { eq: id } };

  // if `due` is used in a condition, CAST the values to DATE.
  if (condition.due) {
    Object.entries(condition.due).forEach(([k, v]) => {
      if (k === 'between') {
        condition.due[k] = v.map((d) => rds.typeHint.DATE(d));
      } else {
        condition.due[k] = rds.typeHint.DATE(v);
      }
    });
  }

  // if a due date is provided, cast is as `DATE`
  if (values.due) {
    values.due = rds.typeHint.DATE(values.due);
  }

  const updateStatement = rds.update({
    table: 'todos',
    values,
    where,
    returning: '*',
  });
  return rds.createPgStatement(updateStatement);
}

export function response(ctx) {
  const { error, result } = ctx;
  if (error) {
    return util.appendError(error.message, error.type, result);
  }
  return rds.toJsonObject(result)[0][0];
}
```

Agora tente uma atualização com uma condição:

```
mutation UPDATE {
  updateTodo(
    input: {
        id: 1, description: "edits: make a change", due: "2023-12-12"},
    condition: {
        description: {beginsWith: "edits"}, due: {ge: "2023-11-08"}})
    {
          description
          due
          id
        }
}
```

### Mutation.deleteTodo
<a name="deletetodo"></a>

É possível `delete` um `Todo` com a mutação `deleteTodo`. Isso funciona como a mutação `updateTodo`, e você deve especificar o `id` do item que deseja excluir da maneira a seguir.

```
mutation DELETE {
  deleteTodo(input: {id: 1}) {
    description
    due
    id
  }
}
```

### Redigir consultas personalizadas
<a name="writing-custom-queries"></a>

Usamos os utilitários do módulo do `rds` para criar declarações SQL. Também podemos redigir a própria declaração estática personalizada para interagir com o banco de dados. Primeiro, atualize o esquema para remover o `id` da entrada `CreateTask`.

```
input CreateTaskInput {
    todoId: Int!
    description: String
}
```

Depois, crie algumas tarefas. Uma tarefa tem uma relação de chave estrangeira com `Todo` da maneira a seguir.

```
mutation TASKS {
  a: createTask(input: {todoId: 2, description: "my first sub task"}) { id }
  b:createTask(input: {todoId: 2, description: "another sub task"}) { id }
  c: createTask(input: {todoId: 2, description: "a final sub task"}) { id }
}
```

Crie um novo campo no tipo `Query` chamado `getTodoAndTasks` da maneira a seguir.

```
getTodoAndTasks(id: Int!): Todo
```

Adicione um campo `tasks` ao tipo `Todo` da maneira a seguir.

```
type Todo {
    due: AWSDate!
    id: Int!
    createdAt: String
    description: String!
    tasks:TaskConnection
}
```

Salve o esquema. No editor de esquemas no console, à direita, selecione **Anexar resolvedor** para `getTodosAndTasks(id: Int!): Todo`. Selecione a fonte de dados do Amazon RDS. Atualize o resolvedor com o código a seguir.

```
import { sql, createPgStatement,toJsonObject } from '@aws-appsync/utils/rds';

export function request(ctx) {
    return createPgStatement(
        sql`SELECT * from todos where id = ${ctx.args.id}`,
        sql`SELECT * from tasks where "todoId" = ${ctx.args.id}`);
}

export function response(ctx) {
    const result = toJsonObject(ctx.result);
    const todo = result[0][0];
    if (!todo) {
        return null;
    }
    todo.tasks = { items: result[1] };
    return todo;
}
```

Nesse código, usamos o modelo de tag `sql` para redigir uma declaração SQL para a qual podemos transmitir com segurança um valor dinâmico em runtime. `createPgStatement` pode receber até duas solicitações SQL por vez. Usamos isso para enviar uma consulta ao `todo` e outra para a `tasks`. É possível fazer isso com uma declaração `JOIN` ou qualquer outro método. A ideia é poder redigir a própria declaração SQL para implementar a lógica de negócios. Para usar a consulta no editor **Consultas**, faça o seguinte.

```
query TodoAndTasks {
  getTodosAndTasks(id: 2) {
    id
    due
    description
    tasks {
      items {
        id
        description
      }
    }
  }
}
```

## Excluir o cluster
<a name="rds-delete-cluster"></a>

**Importante**  
A exclusão de um cluster é permanente. Revise o projeto com cuidado antes de realizar essa ação.

Para excluir o cluster:

```
$ aws rds delete-db-cluster \
    --db-cluster-identifier appsync-tutorial \
    --skip-final-snapshot
```