

Las traducciones son generadas a través de traducción automática. En caso de conflicto entre la traducción y la version original de inglés, prevalecerá la version en inglés.

# Uso de Aurora PostgreSQL con API de datos en AWS AppSync
<a name="aurora-serverless-tutorial-js"></a>

 

Aprenda a conectar su API de GraphQL a las bases de datos de Aurora PostgreSQL mediante. AWS AppSync Esta integración le permite crear aplicaciones escalables y basadas en datos mediante la ejecución de consultas y mutaciones de SQL mediante operaciones de GraphQL. AWS AppSync proporciona una fuente de datos para ejecutar sentencias SQL en los clústeres de Amazon Aurora que están habilitados con una API de datos. Puedes usar AWS AppSync resolutores para ejecutar sentencias SQL en la API de datos con consultas, mutaciones y suscripciones de GraphQL.

Antes de comenzar este tutorial, debes tener una familiaridad básica con los AWS servicios y los conceptos de GraphQL.

**nota**  
En este tutorial se utiliza la región `US-EAST-1`. 

**Topics**
+ [Configuración de la base de datos de Aurora PostgreSQL](#creating-clusters)
+ [Creación de la base de datos y la tabla](#creating-db-table)
+ [Creación de un esquema de GraphQL](#rds-graphql-schema)
+ [Solucionadores para RDS](#rds-resolvers)
+ [Eliminación de su clúster](#rds-delete-cluster)

## Configuración de la base de datos de Aurora PostgreSQL
<a name="creating-clusters"></a>

Antes de añadir una fuente de datos de Amazon RDS a AWS AppSync, haga lo siguiente.

1. Habilite una API de datos en un clúster de Aurora Serverless v2.

1. Configure un secreto mediante AWS Secrets Manager

1. Cree el clúster mediante el siguiente AWS CLI comando.

   ```
   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
   ```

Esto devolverá un ARN para el clúster. Tras crear un clúster, debes añadir una instancia Serverless v2 con el siguiente AWS CLI comando.

```
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**  
Estos puntos de conexión tardan un tiempo en activarse. Puede comprobar su estado en la consola de RDS, en la pestaña **Conectividad y seguridad** del clúster.

Comprueba el estado del clúster con el siguiente AWS CLI comando.

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

Cree un secreto a través de la AWS Secrets Manager consola o AWS CLI con un archivo de entrada como el siguiente mediante `USERNAME` y `COMPLEX_PASSWORD` desde el paso anterior:

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

Pase esto como parámetro a AWS CLI:

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

Esto devolverá un ARN para el secreto. Al crear una fuente de datos en la consola, **anote** el ARN del clúster Aurora Serverless v2 y el secreto para más adelante. AWS AppSync 

## Creación de la base de datos y la tabla
<a name="creating-db-table"></a>

Primero, cree una base de datos denominada `TESTDB`. En PostgreSQL, una base de datos es un contenedor que incluye tablas y otros objetos SQL. Valide que el clúster de Aurora Serverless v2 esté configurado correctamente antes de añadirlo a la API de AWS AppSync . En primer lugar, cree una base de datos *TESTDB* con el parámetro `--sql` de la siguiente forma.

```
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"
```

 Si esto se ejecuta sin errores, añada dos tablas con el 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);'
```

Si se ejecuta correctamente, agregue el clúster como origen de datos en la API.

## Creación de un esquema de GraphQL
<a name="rds-graphql-schema"></a>

Ahora que la API de datos de Aurora Serverless v2 se ejecuta con tablas configuradas, crearemos un esquema de GraphQL. Puede crear la API con rapidez importando las configuraciones de la tabla desde una base de datos existente mediante el asistente de creación de la API.

Para empezar: 

1. En la AWS AppSync consola, elija **Crear API** y, **a continuación, Comenzar con un clúster de Amazon Aurora**. 

1. Especifique los detalles de la API, como el **nombre de la API**, y, a continuación, seleccione su base de datos para generar la API.

1. Seleccione la base de datos. Si es necesario, actualice la región y, a continuación, elija el clúster de Aurora y la base de datos *TESTDB*. 

1. Elija su secreto y, a continuación, seleccione **Importar**. 

1. Una vez descubiertas las tablas, actualice los nombres de tipos. Cambie `Todos` a `Todo` y `Tasks` a `Task`. 

1. Obtenga una vista previa del esquema generado seleccionando **Vista previa del esquema**. Sus esquema tendrá un aspecto similar al siguiente: 

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

1. Para el rol, puede AWS AppSync crear un rol nuevo o crear uno con una política similar a la siguiente:

------
#### [ 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:*"
               ]
           }
       ]
   }
   ```

------

   Tenga en cuenta que hay dos instrucciones en esta política a las que está concediendo acceso de rol. El primer recurso es su clúster de Aurora y el segundo es su ARN de AWS Secrets Manager . 

   Elija **Siguiente**, revise los detalles de configuración y, a continuación, elija **Crear API**. Ahora puede disponer de una API totalmente operativa. Puede revisar todos los detalles de su API en la página **Esquema**. 

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

El flujo de creación de la API creó automáticamente los solucionadores para que interactuaran con nuestros tipos. Si consulta la página **Esquema**, encontrará algunos de los siguientes solucionadores.
+ Crear un `todo` mediante el campo `Mutation.createTodo`.
+ Actualizar un `todo` mediante el campo `Mutation.updateTodo`.
+ Eliminar un `todo` mediante el campo `Mutation.deleteTodo`.
+ Obtener un `todo` individual mediante el campo `Query.getTodo`.
+ Enumerar todos `todos` mediante el campo `Query.listTodos`.

Encontrará campos y solucionadores similares adjuntos para el tipo `Task`. Echemos un vistazo más de cerca a algunos de los solucionadores. 

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

En el editor de esquemas de la AWS AppSync consola, en la parte derecha, selecciona `testdb` junto a`createTodo(...): Todo`. El código de resolución utiliza la función `insert` del módulo `rds` para crear dinámicamente una instrucción de inserción que añade datos a la tabla `todos`. Puesto que estamos trabajando con Postgres, podemos aprovechar la instrucción `returning` para recuperar los datos insertados.

Actualice el siguiente solucionador para especificar correctamente el tipo `DATE` del 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]
}
```

Guarde el solucionador. La sugerencia de tipo marca el `due` correctamente en nuestro objeto de entrada como un tipo `DATE`. Esto permite que el motor Postgres interprete correctamente el valor. A continuación, actualice su esquema para eliminar el `id` de la entrada `CreateTodo`. Puesto que nuestra base de datos de Postgres puede devolver el ID generado, puede confiar en él para crear y devolver el resultado como una única solicitud de la siguiente forma.

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

Realice el cambio y actualice su esquema. Diríjase al editor **Consultas** para agregar un elemento a la base de datos de la siguiente forma.

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

Obtendrá el siguiente resultado.

```
{
  "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>

En el editor de esquemas de la consola, en el lado derecho, elija `testdb` junto a `listTodos(id: ID!): Todo`. El controlador de solicitudes utiliza la función de utilidad select para crear una solicitud de forma dinámica en tiempo de ejecución.

```
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` en función de la fecha `due`. Actualicemos la resolución para convertir los valores `due` en `DATE`. Actualice la lista de importaciones y el controlador de solicitudes de la siguiente forma.

```
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 };
}
```

En el editor **Consultas**, haga lo siguiente:

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

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

También puede `update` a `Todo`. En el editor de **consultas**, vamos a actualizar nuestro primer elemento `Todo` de `id` `1`.

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

Tenga en cuenta que debe especificar el `id` del elemento que está actualizando. También puede especificar una condición para actualizar únicamente un elemento que cumpla condiciones específicas. Por ejemplo, es posible que solo queramos editar de la siguiente forma el elemento si la descripción comienza por `edits`.

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

Al igual que gestionamos nuestras operaciones `create` y `list`, podemos actualizar nuestro solucionador para convertir el campo `due` en una `DATE`. Guarde estos cambios en `updateTodo` de la siguiente forma.

```
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];
}
```

Ahora intente realizar una actualización con una condición:

```
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>

Puede `delete` un `Todo` con la mutación `deleteTodo`. Funciona igual que la mutación `updateTodo`, y debe especificar el `id` del elemento que desea eliminar de la siguiente forma.

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

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

Hemos utilizado las utilidades del módulo `rds` para crear nuestras instrucciones SQL. También podemos escribir nuestra propia instrucción estática personalizada para interactuar con nuestra base de datos. En primer lugar, actualice el esquema para eliminar el campo `id` de la entrada `CreateTask`.

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

A continuación, cree un par de tareas. Una tarea tiene una relación de clave externa con `Todo` de la siguiente forma.

```
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 }
}
```

Cree un campo nuevo de su tipo `Query` denominado `getTodoAndTasks` de la siguiente forma.

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

Agregue un campo `tasks` al tipo `Todo` de la siguiente forma.

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

Guarde el esquema. En el editor de esquemas de la consola, elija a la derecha **Asociar solucionador** para `getTodosAndTasks(id: Int!): Todo`. Elija el origen de datos de Amazon RDS. Actualice su solucionador con el siguiente código.

```
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;
}
```

En este código, utilizamos la plantilla de etiquetas de `sql` para escribir una instrucción SQL a la que podamos pasar un valor dinámico de forma segura en tiempo de ejecución. `createPgStatement` puede aceptar hasta dos solicitudes de SQL a la vez. La usamos para enviar una consulta para nuestra `todo` y otra para nuestras `tasks`. Podría haberlo hecho con una instrucción `JOIN` o con cualquier otro método. La idea es poder escribir su propia instrucción SQL para implementar su lógica empresarial. Para usar la consulta en el editor **Consultas**, haga lo siguiente.

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

## Eliminación de su clúster
<a name="rds-delete-cluster"></a>

**importante**  
La eliminación de un clúster es permanente. Revise su proyecto detenidamente antes de llevar a cabo esta acción.

Para eliminar el clúster:

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