

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.

# JavaScript tutoriales de resolución para AWS AppSync
<a name="tutorials-js"></a>

Las fuentes de datos y los resolutores se utilizan AWS AppSync para traducir las solicitudes de GraphQL y obtener información de sus recursos. AWS AWS AppSync admite el aprovisionamiento automático y las conexiones con determinados tipos de fuentes de datos. AWS AppSync también admite Amazon DynamoDB AWS Lambda, bases de datos relacionales (Amazon Aurora Serverless), OpenSearch Amazon Service y puntos de enlace HTTP como fuentes de datos. Puedes usar una API de GraphQL con tus AWS recursos existentes o crear fuentes de datos y resolutores desde cero. Las siguientes secciones están destinadas a explicar algunos de los casos de uso más comunes de GraphQL en forma de tutoriales.

**Topics**
+ [Creación de una aplicación posterior sencilla con resolutores de DynamoDB JavaScript](tutorial-dynamodb-resolvers-js.md)
+ [Uso de resolutores AWS Lambda](tutorial-lambda-resolvers-js.md)
+ [Uso de solucionadores locales](tutorial-local-resolvers-js.md)
+ [Combinación de solucionadores de GraphQL](tutorial-combining-graphql-resolvers-js.md)
+ [Uso de solucionadores OpenSearch de servicios](tutorial-elasticsearch-resolvers-js.md)
+ [Ejecución de transacciones de DynamoDB](tutorial-dynamodb-transact-js.md)
+ [Uso de operaciones por lotes de DynamoDB](tutorial-dynamodb-batch-js.md)
+ [Uso de solucionadores de HTTP](tutorial-http-resolvers-js.md)
+ [Uso de Aurora PostgreSQL con la API de datos](aurora-serverless-tutorial-js.md)

# Creación de una aplicación posterior sencilla con resolutores de DynamoDB JavaScript
<a name="tutorial-dynamodb-resolvers-js"></a>

En este tutorial, importará sus tablas de Amazon DynamoDB y las conectará AWS AppSync para crear una API de GraphQL totalmente funcional mediante solucionadores de canalización que puede JavaScript utilizar en su propia aplicación.

Utilizará la AWS AppSync consola para aprovisionar sus recursos de Amazon DynamoDB, crear sus resolutores y conectarlos a sus fuentes de datos. También podrá leer y escribir en la base de datos de Amazon DynamoDB a través de instrucciones de GraphQL y suscribirse a datos en tiempo real.

Existen pasos específicos que deben completarse para que las instrucciones de GraphQL se traduzcan a operaciones de Amazon DynamoDB y para que las respuestas se vuelvan a traducir a GraphQL. En este tutorial se describe el proceso de configuración a través de varios escenarios y patrones de acceso a datos del mundo real.

## Creación de la API de GraphQL
<a name="create-graphql-api"></a>

**Para crear una API de GraphQL en AWS AppSync**

1. Abre la AppSync consola y selecciona **Crear API**.

1. Seleccione **Diseñar desde cero** y elija **Siguiente**.

1. Llame a su API `PostTutorialAPI` y, a continuación, seleccione **Siguiente**. Vaya a la página de revisión dejando el resto de las opciones con sus valores predeterminados y seleccione `Create`.

La AWS AppSync consola crea una nueva API de GraphQL para usted. De forma predeterminada, utiliza el modo de autenticación con clave de API. Puede utilizar la consola para configurar el resto de la API de GraphQL y ejecutar consultas en ella durante el resto de este tutorial.

## Definición de una API de publicación básica
<a name="define-post-api"></a>

Ahora que tiene una API de GraphQL, puede configurar un esquema básico que permita la creación, recuperación y eliminación básica de datos de publicaciones.

**Para añadir datos a su esquema**

1. En su API, seleccione la pestaña **Esquema**.

1. Crearemos un esquema que defina un tipo `Post` y una operación `addPost` para añadir y obtener objetos `Post`. En la página **Esquema**, sustituya el contenido por el código siguiente:

   ```
   schema {
       query: Query
       mutation: Mutation
   }
   
   type Query {
       getPost(id: ID): Post
   }
   
   type Mutation {
       addPost(
           id: ID!
           author: String!
           title: String!
           content: String!
           url: String!
       ): Post!
   }
   
   type Post {
       id: ID!
       author: String
       title: String
       content: String
       url: String
       ups: Int!
       downs: Int!
       version: Int!
   }
   ```

1. Elija **Save Schema (Guardar esquema)**.

## Configuración de la tabla de Amazon DynamoDB
<a name="configure-dynamodb"></a>

La AWS AppSync consola puede ayudarle a aprovisionar los AWS recursos necesarios para almacenar sus propios recursos en una tabla de Amazon DynamoDB. En este paso, creará una tabla de Amazon DynamoDB para almacenar sus publicaciones. También configurará un [índice secundario](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SecondaryIndexes.html) que utilizaremos más adelante.

**Para crear una tabla de Amazon DynamoDB**

1. En la página **Esquema**, seleccione **Crear recursos**.

1. Elija **Usar tipo existente** y, a continuación, elija el tipo `Post`.

1. En la sección **Índices secundarios**, elija **Añadir índice**.

1. Llame al índice `author-index`.

1. Establezca el `Primary key` en `author` y la clave `Sort` en `None`.

1. Deshabilite **Generación automática de GraphQL**. En este ejemplo, vamos a crear el solucionador nosotros mismos.

1. Seleccione **Crear**.

Ahora tiene un nuevo origen de datos llamado `PostTable`, que puede ver en **Orígenes de datos** en la pestaña lateral. Este origen de datos se utiliza para vincular las consultas y mutaciones a la tabla de Amazon DynamoDB. 

## Configuración de un solucionador de AddPost (Amazon DynamoDB) PutItem
<a name="configure-addpost"></a>

Ahora que AWS AppSync conoce la tabla Amazon DynamoDB, puede vincularla a consultas y mutaciones individuales definiendo resolutores. La primera resolución que cree es la resolución de `addPost` canalización que utiliza JavaScript, que le permite crear una publicación en la tabla de Amazon DynamoDB. Un solucionador de canalización tiene los siguientes componentes: 
+ La ubicación en el esquema de GraphQL donde se asocia el solucionador. En este caso configuramos en el campo `createPost` un solucionador de tipo `Mutation`. El solucionador se invocará cuando el intermediario llame a la mutación `{ addPost(...){...} }`. 
+ El origen de datos que se va a utilizar para el solucionador. En este caso, queremos utilizar el origen de datos de DynamoDB definido anteriormente para poder añadir entradas en la tabla `post-table-for-tutorial` de DynamoDB.
+ El controlador de solicitudes. El controlador de solicitudes es una función que gestiona la solicitud entrante de la persona que llama y la traduce en instrucciones AWS AppSync para ejecutarlas en DynamoDB.
+ El controlador de respuestas. El cometido del controlador de respuestas es gestionar la respuesta de DynamoDB y traducirla de nuevo a algo que GraphQL espera. Esto resulta útil si la forma de los datos en DynamoDB es diferente del tipo `Post` en GraphQL. Como este caso sí tienen la misma forma, solo hay que transmitir los datos. 

**Para configurar el solucionador**

1. En su API, seleccione la pestaña **Esquema**.

1. En el panel **Solucionadores**, busque el campo `addPost` debajo del tipo `Mutation` y, a continuación, seleccione **Asociar**.

1. Elija el origen de datos y, a continuación, seleccione **Crear**.

1. En su editor de código, reemplace el código por este fragmento:

   ```
   import { util } from '@aws-appsync/utils'
   import * as ddb from '@aws-appsync/utils/dynamodb'
   
   export function request(ctx) {
   	const item = { ...ctx.arguments, ups: 1, downs: 0, version: 1 }
   	const key = { id: ctx.args.id ?? util.autoId() }
   	return ddb.put({ key, item })
   }
   
   export function response(ctx) {
   	return ctx.result
   }
   ```

1. Seleccione **Save**.

**nota**  
En este código, se emplean las utilidades del módulo de DynamoDB que permiten crear fácilmente solicitudes de DynamoDB.

AWS AppSync viene con una utilidad para la generación automática de ID llamada`util.autoId()`, que se utiliza para generar una ID para su nueva publicación. Si no especifica un identificador, la utilidad lo generará automáticamente.

```
const key = { id: ctx.args.id ?? util.autoId() }
```

Para obtener más información sobre las utilidades disponibles JavaScript, consulte [las características JavaScript de tiempo de ejecución para resoluciones y funciones](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-util-reference-js.html). 

### Llame a la API para añadir una publicación
<a name="call-api-addpost"></a>

Ahora que se ha configurado el solucionador, AWS AppSync puede convertir una `addPost` mutación entrante en una operación de Amazon `PutItem` DynamoDB. Ahora puede ejecutar una mutación para incluir datos en la tabla.

**Para ejecutar la operación**

1. En su API, seleccione la pestaña **Consultas**.

1. En el panel **Consultas**, pegue la mutación siguiente:

   ```
   mutation addPost {
     addPost(
       id: 123,
       author: "AUTHORNAME"
       title: "Our first post!"
       content: "This is our first post."
       url: "https://aws.amazon.com/appsync/"
     ) {
       id
       author
       title
       content
       url
       ups
       downs
       version
     }
   }
   ```

1. Seleccione **Ejecutar** (el botón de reproducción naranja) y, a continuación, elija `addPost`. Los resultados de la publicación que acaba de crear deben aparecer en el panel **Resultados** a la derecha del panel **Consultas**. Debería parecerse a lo que sigue:

   ```
   {
     "data": {
       "addPost": {
         "id": "123",
         "author": "AUTHORNAME",
         "title": "Our first post!",
         "content": "This is our first post.",
         "url": "https://aws.amazon.com/appsync/",
         "ups": 1,
         "downs": 0,
         "version": 1
       }
     }
   }
   ```

La siguiente explicación muestra lo que ocurrió:

1. AWS AppSync recibió una solicitud de `addPost` mutación.

1. AWS AppSync ejecuta el gestor de solicitudes del solucionador. La función `ddb.put` crea una solicitud `PutItem` similar a la siguiente:

   ```
   {
     operation: 'PutItem',
     key: { id: { S: '123' } },
     attributeValues: {
       downs: { N: 0 },
       author: { S: 'AUTHORNAME' },
       ups: { N: 1 },
       title: { S: 'Our first post!' },
       version: { N: 1 },
       content: { S: 'This is our first post.' },
       url: { S: 'https://aws.amazon.com/appsync/' }
     }
   }
   ```

1. AWS AppSync utiliza este valor para generar y ejecutar una solicitud de Amazon `PutItem` DynamoDB.

1. AWS AppSync tomó los resultados de la `PutItem` solicitud y los volvió a convertir a tipos GraphQL.

   ```
   {
       "id" : "123",
       "author": "AUTHORNAME",
       "title": "Our first post!",
       "content": "This is our first post.",
       "url": "https://aws.amazon.com/appsync/",
       "ups" : 1,
       "downs" : 0,
       "version" : 1
   }
   ```

1. El controlador de respuestas devuelve el resultado inmediatamente (`return ctx.result`).

1. El resultado final es visible en la respuesta de GraphQL.

## Configuración del solucionador GetPost (Amazon DynamoDB) GetItem
<a name="configure-getpost"></a>

Ahora que puede añadir datos a la tabla de Amazon DynamoDB, tiene que configurar la consulta `getPost` para poder recuperar los datos de la tabla. Para ello, tiene que configurar otro solucionador.

**Para añadir su solucionador**

1. En su API, seleccione la pestaña **Esquema**.

1. En el panel **Solucionadores** a la derecha, busque el campo `getPost` en el tipo `Query` y, a continuación, seleccione **Asociar**.

1. Elija el origen de datos y, a continuación, seleccione **Crear**.

1. En el editor de código, sustituya el código por este fragmento:

   ```
   import * as ddb from '@aws-appsync/utils/dynamodb'
   	
   export function request(ctx) {
   	return ddb.get({ key: { id: ctx.args.id } })
   }
   
   export const response = (ctx) => ctx.result
   ```

1. Guarde el solucionador.

**nota**  
En este solucionador, utilizamos una expresión de función de flecha para el controlador de respuestas.

### Llame a la API para obtener una publicación
<a name="call-api-getpost"></a>

Ahora que el solucionador está configurado, AWS AppSync sabe cómo convertir una `getPost` consulta entrante en una operación de Amazon `GetItem` DynamoDB. Ahora puede ejecutar una consulta para recuperar la publicación que ha creado anteriormente.

**Para ejecutar su consulta**

1. En su API, seleccione la pestaña **Consultas**. 

1. En el panel **Consultas**, añada el siguiente código y use el identificador que copió después de crear su publicación:

   ```
   query getPost {
     getPost(id: "123") {
       id
       author
       title
       content
       url
       ups
       downs
       version
     }
   }
   ```

1. Seleccione **Ejecutar** (el botón de reproducción naranja) y, a continuación, elija `getPost`. Los resultados de la publicación que acaba de crear deben aparecer en el panel **Resultados** a la derecha del panel **Consultas**.

1. Los resultados obtenidos de Amazon DynamoDB deben aparecer en el panel **Resultados** a la derecha del panel **Consultas**. Debería parecerse a lo que sigue:

   ```
   {
     "data": {
       "getPost": {
         "id": "123",
         "author": "AUTHORNAME",
         "title": "Our first post!",
         "content": "This is our first post.",
         "url": "https://aws.amazon.com/appsync/",
         "ups": 1,
         "downs": 0,
         "version": 1
       }
     }
   }
   ```

Como alternativa, vamos a tomar el siguiente ejemplo:

```
query getPost {
  getPost(id: "123") {
    id
    author
    title
  }
}
```

Si la consulta `getPost` solo necesita el `id`, `author` y `title`, puede cambiar la función de solicitud para utilizar expresiones de proyección que especifiquen únicamente los atributos que desee de la tabla de DynamoDB y evitar la transferencia innecesaria de datos de DynamoDB a AWS AppSync. Por ejemplo, la función de solicitud puede tener un aspecto similar al siguiente fragmento:

```
import * as ddb from '@aws-appsync/utils/dynamodb'

export function request(ctx) {
	return ddb.get({
		key: { id: ctx.args.id },
		projection: ['author', 'id', 'title'],
	})
}

export const response = (ctx) => ctx.result
```

También puede usar un [selectionSetList](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-context-reference-js.html#aws-appsync-resolver-context-reference-info-js)ancho `getPost` para representar lo siguiente: `expression`

```
import * as ddb from '@aws-appsync/utils/dynamodb'

export function request(ctx) {
	const projection = ctx.info.selectionSetList.map((field) => field.replace('/', '.'))
	return ddb.get({ key: { id: ctx.args.id }, projection })
}

export const response = (ctx) => ctx.result
```

## Crear una mutación de UpdatePost (Amazon DynamoDB) UpdateItem
<a name="configure-updatepost"></a>

De momento, ya sabe crear y recuperar objetos `Post` en Amazon DynamoDB. A continuación, va a configurar una nueva mutación para actualizar objetos. En comparación con la mutación `addPost`, que requiere que se especifiquen todos los campos, esta mutación le permite especificar solo los campos que desea cambiar. También introdujo un nuevo argumento `expectedVersion` que permite especificar la versión que se quiere modificar. Va a configurar una condición que garantice que está modificando la versión más reciente del objeto. Para ello, utilizará la operación `UpdateItem` de Amazon DynamoDB.

**Para actualizar su solucionador**

1. En su API, seleccione la pestaña **Esquema**.

1. En el panel **Schema (Esquema)** modifique el tipo `Mutation` para agregar una nueva mutación `updatePost` de este modo:

   ```
   type Mutation {
       updatePost(
           id: ID!,
           author: String,
           title: String,
           content: String,
           url: String,
           expectedVersion: Int!
       ): Post
       
       addPost(
           id: ID
           author: String!
           title: String!
           content: String!
           url: String!
       ): Post!
   }
   ```

1. Elija **Save Schema (Guardar esquema)**.

1. En el panel **Solucionadores** de la derecha, busque el campo `updatePost` recién creado en el tipo `Mutation` y, a continuación, seleccione **Asociar**. Cree su nuevo solucionador con el siguiente fragmento:

   ```
   import { util } from '@aws-appsync/utils';
   import * as ddb from '@aws-appsync/utils/dynamodb';
   
   export function request(ctx) {
     const { id, expectedVersion, ...rest } = ctx.args;
     const values = Object.entries(rest).reduce((obj, [key, value]) => {
       obj[key] = value ?? ddb.operations.remove();
       return obj;
     }, {});
   
     return ddb.update({
       key: { id },
       condition: { version: { eq: expectedVersion } },
       update: { ...values, version: ddb.operations.increment(1) },
     });
   }
   
   export function response(ctx) {
     const { error, result } = ctx;
     if (error) {
       util.appendError(error.message, error.type);
     }
     return result;
   ```

1. Guarde los cambios que haya realizado.

Este solucionador utiliza `ddb.update` para crear una solicitud `UpdateItem` de Amazon DynamoDB. En lugar de escribir el elemento completo, solo pide a Amazon DynamoDB que actualice determinados atributos. Esto se consigue mediante expresiones de actualización de Amazon DynamoDB.

La función `ddb.update` toma como argumentos una clave y un objeto de actualización. A continuación, se comprueban los valores de los argumentos entrantes. Cuando un valor esté establecido en `null`, utilice la operación `remove` de DynamoDB para indicar que el valor debe eliminarse del elemento de DynamoDB.

También hay una nueva sección `condition`. Una expresión de condición le permite indicar AWS AppSync a Amazon DynamoDB si la solicitud debe realizarse correctamente o no en función del estado del objeto que ya se encuentra en Amazon DynamoDB antes de realizar la operación. En este caso, solo quiere que la solicitud `UpdateItem` se realice si el campo `version` del elemento que hay en Amazon DynamoDB coincide exactamente con el argumento `expectedVersion`. Cuando se actualice el elemento, queremos incrementar el valor de `version`. Esto es fácil de hacer con la función de operación `increment`.

Si desea más información sobre las expresiones de condición, consulte la documentación de [expresiones de condición](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-condition-expressions).

Para obtener más información sobre la `UpdateItem` solicitud, consulte la [UpdateItem](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-updateitem)documentación y la documentación del [módulo DynamoDB](https://docs.aws.amazon.com/appsync/latest/devguide/built-in-modules-js.html). 

Para obtener más información sobre cómo escribir expresiones de actualización, consulte la documentación de [ UpdateExpressionsDynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html).

### Llame a la API para actualizar una publicación
<a name="call-api-updatepost"></a>

Vamos a intentar actualizar el objeto `Post` con el nuevo solucionador.

**Para actualizar el objeto**

1. En su API, seleccione la pestaña **Consultas**.

1. En el panel **Consultas**, pegue la mutación siguiente. También tendrá que actualizar el argumento `id` con el valor que anotó anteriormente:

   ```
   mutation updatePost {
     updatePost(
       id:123
       title: "An empty story"
       content: null
       expectedVersion: 1
     ) {
       id
       author
       title
       content
       url
       ups
       downs
       version
     }
   }
   ```

1. Seleccione **Ejecutar** (el botón de reproducción naranja) y, a continuación, elija `updatePost`.

1. La publicación actualizada en Amazon DynamoDB debería aparecer en el panel **Resultados** a la derecha del panel **Consultas**. Debería parecerse a lo que sigue:

   ```
   {
     "data": {
       "updatePost": {
         "id": "123",
         "author": "A new author",
         "title": "An empty story",
         "content": null,
         "url": "https://aws.amazon.com/appsync/",
         "ups": 1,
         "downs": 0,
         "version": 2
       }
     }
   }
   ```

En esta solicitud, ha pedido a Amazon DynamoDB que actualice `title` únicamente los campos AWS AppSync y. `content` Todos los demás campos se han quedado como estaban (salvo por el incremento del campo `version`). Hemos establecido un nuevo valor en el atributo `title` y hemos eliminado el atributo `content` de la publicación. Los campos `author`, `url`, `ups` y `downs` no se han modificado. Intente ejecutar la solicitud de mutación de nuevo, dejándola exactamente como está. Verá una respuesta parecida a la siguiente:

```
{
  "data": {
    "updatePost": null
  },
  "errors": [
    {
      "path": [
        "updatePost"
      ],
      "data": null,
      "errorType": "DynamoDB:ConditionalCheckFailedException",
      "errorInfo": null,
      "locations": [
        {
          "line": 2,
          "column": 3,
          "sourceName": null
        }
      ],
      "message": "The conditional request failed (Service: DynamoDb, Status Code: 400, Request ID: 1RR3QN5F35CS8IV5VR4OQO9NNBVV4KQNSO5AEMVJF66Q9ASUAAJG)"
    }
  ]
}
```

La solicitud falla porque la expresión de condición se evalúa como `false`: 

1. La primera vez que ejecutamos la solicitud, el valor del campo `version` de la publicación en Amazon DynamoDB era `1`, que coincidía con el argumento `expectedVersion`. La solicitud se realizó correctamente, lo que significa que el campo `version` se incrementó en Amazon DynamoDB a `2`.

1. La segunda vez que ejecutamos la solicitud, el valor del campo `version` de la publicación en Amazon DynamoDB era `2`, que no coincide con el argumento `expectedVersion`.

Este método suele denominarse *"bloqueo optimista"*.

## Crear mutaciones de votos (Amazon DynamoDB UpdateItem)
<a name="configure-vote-mutations"></a>

El tipo `Post` contiene los campos `ups` y `downs` para permitir el registro de votos a favor y en contra. Sin embargo, en este momento la API no nos permite hacer nada con ellos. Vamos a añadir una mutación para votar a favor o en contra de las publicaciones.

**Para añadir su mutación**

1. En su API, seleccione la pestaña **Esquema**.

1. En el panel **Esquema**, modifique el tipo `Mutation` y añada la enumeración `DIRECTION` para añadir nuevas mutaciones de votos:

   ```
   type Mutation {
       vote(id: ID!, direction: DIRECTION!): Post
       updatePost(
           id: ID!,
           author: String,
           title: String,
           content: String,
           url: String,
           expectedVersion: Int!
       ): Post
       addPost(
           id: ID,
           author: String!,
           title: String!,
           content: String!,
           url: String!
       ): Post!
   }
   
   enum DIRECTION {
     UP
     DOWN
   }
   ```

1. Elija **Save Schema (Guardar esquema)**.

1. En el panel **Solucionadores** de la derecha, busque el campo `vote` recién creado en el tipo `Mutation` y, a continuación, seleccione **Asociar**. Para crear un nuevo solucionador, cree y reemplace el código por el siguiente fragmento:

   ```
   import * as ddb from '@aws-appsync/utils/dynamodb';
   
   export function request(ctx) {
     const field = ctx.args.direction === 'UP' ? 'ups' : 'downs';
     return ddb.update({
       key: { id: ctx.args.id },
       update: {
         [field]: ddb.operations.increment(1),
         version: ddb.operations.increment(1),
       },
     });
   }
   
   export const response = (ctx) => ctx.result;
   ```

1. Guarde los cambios que haya realizado.

### Llame a la API para votar a favor o en contra de una publicación
<a name="call-api-vote"></a>

Ahora que se han configurado los nuevos resolutores, AWS AppSync sabe cómo convertir una operación entrante `upvotePost` o una `downvote` mutación en una operación de Amazon DynamoDB`UpdateItem`. Ahora puede ejecutar mutaciones para votar a favor o en contra de la publicación que ha creado anteriormente.

**Para ejecutar su mutación**

1. En su API, seleccione la pestaña **Consultas**.

1. En el panel **Consultas**, pegue la mutación siguiente. También tendrá que actualizar el argumento `id` con el valor que anotó anteriormente:

   ```
   mutation votePost {
     vote(id:123, direction: UP) {
       id
       author
       title
       content
       url
       ups
       downs
       version
     }
   }
   ```

1. Seleccione **Ejecutar** (el botón de reproducción naranja) y, a continuación, elija `votePost`.

1. La publicación actualizada en Amazon DynamoDB debería aparecer en el panel **Resultados** a la derecha del panel **Consultas**. Debería parecerse a lo que sigue:

   ```
   {
     "data": {
       "vote": {
         "id": "123",
         "author": "A new author",
         "title": "An empty story",
         "content": null,
         "url": "https://aws.amazon.com/appsync/",
         "ups": 6,
         "downs": 0,
         "version": 4
       }
     }
   }
   ```

1. Seleccione **Ejecutar** unas cuantas veces más. Debería ver cómo se incrementan en `1` los campos `ups` y `version` cada vez que ejecuta la consulta.

1. Cambie la consulta para llamarla con una `DIRECTION` diferente.

   ```
   mutation votePost {
     vote(id:123, direction: DOWN) {
       id
       author
       title
       content
       url
       ups
       downs
       version
     }
   }
   ```

1. Seleccione **Ejecutar** (el botón de reproducción naranja) y, a continuación, elija `votePost`.

   En esta ocasión, debería ver cómo se incrementan en `1` los campos `downs` y `version` cada vez que ejecuta la consulta.

## Configuración de un solucionador DeletePost (Amazon DynamoDB) DeleteItem
<a name="configure-deletepost"></a>

A continuación, hay que crear una mutación para eliminar una publicación. Para ello, utilizará la operación `DeleteItem` de Amazon DynamoDB.

**Para añadir su mutación**

1. En el esquema, seleccione la pestaña **Esquema**.

1. En el panel **Esquema** modifique el tipo `Mutation` para añadir una nueva mutación `deletePost`:

   ```
   type Mutation {
       deletePost(id: ID!, expectedVersion: Int): Post
       vote(id: ID!, direction: DIRECTION!): Post
       updatePost(
           id: ID!,
           author: String,
           title: String,
           content: String,
           url: String,
           expectedVersion: Int!
       ): Post
       addPost(
           id: ID
           author: String!,
           title: String!,
           content: String!,
           url: String!
       ): Post!
   }
   ```

1. Esta vez, ha hecho que el campo `expectedVersion` sea opcional. Elija **Guardar esquema**.

1. En el panel **Solucionadores** de la derecha, busque el campo `delete` recién creado en el tipo `Mutation` y, a continuación, seleccione **Asociar**. Cree un nuevo solucionador con el siguiente código:

   ```
   import { util } from '@aws-appsync/utils'
   
   import { util } from '@aws-appsync/utils';
   import * as ddb from '@aws-appsync/utils/dynamodb';
   
   export function request(ctx) {
     let condition = null;
     if (ctx.args.expectedVersion) {
       condition = {
         or: [
           { id: { attributeExists: false } },
           { version: { eq: ctx.args.expectedVersion } },
         ],
       };
     }
     return ddb.remove({ key: { id: ctx.args.id }, condition });
   }
   
   export function response(ctx) {
     const { error, result } = ctx;
     if (error) {
       util.appendError(error.message, error.type);
     }
     return result;
   }
   ```
**nota**  
El argumento `expectedVersion` es opcional. Si el intermediario ha establecido un argumento `expectedVersion` en la solicitud, entonces el controlador de solicitudes añade una condición que solo permitirá que la solicitud `DeleteItem` se atienda cuando el elemento se haya eliminado o cuando el atributo `version` de la publicación en Amazon DynamoDB coincida exactamente con `expectedVersion`. Si se omite, no se especificará ninguna expresión de condición para la solicitud `DeleteItem`. Se realizará correctamente independientemente del valor de `version` o de si el elemento existe o no en Amazon DynamoDB.  
Aunque se trate de eliminar un elemento, puede devolver el elemento eliminado, siempre que no se haya eliminado previamente.

Para obtener más información sobre la `DeleteItem` solicitud, consulte la documentación. [DeleteItem](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-deleteitem)

### Llame la API para eliminar una publicación
<a name="call-api-delete"></a>

Ahora que el solucionador está configurado, AWS AppSync sabe cómo convertir una `delete` mutación entrante en una operación de Amazon `DeleteItem` DynamoDB. Ahora ya podemos ejecutar una mutación para eliminar datos de la tabla.

**Para ejecutar su mutación**

1. En su API, seleccione la pestaña **Consultas**.

1. En el panel **Consultas**, pegue la mutación siguiente. También tendrá que actualizar el argumento `id` con el valor que anotó anteriormente:

   ```
   mutation deletePost {
     deletePost(id:123) {
       id
       author
       title
       content
       url
       ups
       downs
       version
     }
   }
   ```

1. Seleccione **Ejecutar** (el botón de reproducción naranja) y, a continuación, elija `deletePost`.

1. La publicación se elimina de Amazon DynamoDB. **Tenga en cuenta que AWS AppSync devuelve el valor del elemento que se eliminó de Amazon DynamoDB, que debería aparecer en **el panel de resultados, a la** derecha del panel de consultas.** Debería parecerse a lo que sigue:

   ```
   {
     "data": {
       "deletePost": {
         "id": "123",
         "author": "A new author",
         "title": "An empty story",
         "content": null,
         "url": "https://aws.amazon.com/appsync/",
         "ups": 6,
         "downs": 4,
         "version": 12
       }
     }
   }
   ```

1. El valor solo se devuelve si esta llamada a `deletePost` es la que realmente lo elimina de Amazon DynamoDB. Vuelva a seleccionar **Ejecutar**.

1. La llamada se sigue ejecutando con éxito, pero no se devuelve ningún valor:

   ```
   {
     "data": {
       "deletePost": null
     }
   }
   ```

1. Ahora vamos a probar a eliminar una publicación, pero esta vez especificando `expectedValue`. Primero tiene que crear una nueva, porque acaba de eliminar la publicación con la que ha estado trabajando hasta ahora.

1. En el panel **Consultas**, pegue la mutación siguiente:

   ```
   mutation addPost {
     addPost(
       id:123
       author: "AUTHORNAME"
       title: "Our second post!"
       content: "A new post."
       url: "https://aws.amazon.com/appsync/"
     ) {
       id
       author
       title
       content
       url
       ups
       downs
       version
     }
   }
   ```

1. Seleccione **Ejecutar** (el botón de reproducción naranja) y, a continuación, elija `addPost`.

1. Los resultados de la publicación que acaba de crear deben aparecer en el panel **Resultados** a la derecha del panel **Consultas**. Registre el `id` del objeto recién creado, pues lo necesitará en breve. Debería parecerse a lo que sigue:

   ```
   {
     "data": {
       "addPost": {
         "id": "123",
         "author": "AUTHORNAME",
         "title": "Our second post!",
         "content": "A new post.",
         "url": "https://aws.amazon.com/appsync/",
         "ups": 1,
         "downs": 0,
         "version": 1
       }
     }
   }
   ```

1. Ahora, intentemos eliminar esa publicación con un valor ilegal para **expectedVersion**. En el panel **Consultas**, pegue la mutación siguiente. También tendrá que actualizar el argumento `id` con el valor que anotó anteriormente:

   ```
   mutation deletePost {
     deletePost(
       id:123
       expectedVersion: 9999
     ) {
       id
       author
       title
       content
       url
       ups
       downs
       version
     }
   }
   ```

1. Seleccione **Ejecutar** (el botón de reproducción naranja) y, a continuación, elija `deletePost`. Se devuelve el siguiente resultado:

   ```
   {
     "data": {
       "deletePost": null
     },
     "errors": [
       {
         "path": [
           "deletePost"
         ],
         "data": null,
         "errorType": "DynamoDB:ConditionalCheckFailedException",
         "errorInfo": null,
         "locations": [
           {
             "line": 2,
             "column": 3,
             "sourceName": null
           }
         ],
         "message": "The conditional request failed (Service: DynamoDb, Status Code: 400, Request ID: 7083O037M1FTFRK038A4CI9H43VV4KQNSO5AEMVJF66Q9ASUAAJG)"
       }
     ]
   }
   ```

1. La solicitud ha fallado porque la expresión de condición se evalúa como `false`. El valor `version` de la publicación en Amazon DynamoDB no coincide con el `expectedValue` especificado en los argumentos. El valor actual del objeto se devuelve en el campo `data` de la sección `errors` de la respuesta de GraphQL. Vuelva a intentar la solicitud, pero corrija `expectedVersion`: 

   ```
   mutation deletePost {
     deletePost(
       id:123
       expectedVersion: 1
     ) {
       id
       author
       title
       content
       url
       ups
       downs
       version
     }
   }
   ```

1. Seleccione **Ejecutar** (el botón de reproducción naranja) y, a continuación, elija `deletePost`. 

   Esta vez la solicitud se realiza correctamente y se devuelve el valor eliminado de Amazon DynamoDB:

   ```
   {
     "data": {
       "deletePost": {
         "id": "123",
         "author": "AUTHORNAME",
         "title": "Our second post!",
         "content": "A new post.",
         "url": "https://aws.amazon.com/appsync/",
         "ups": 1,
         "downs": 0,
         "version": 1
       }
     }
   }
   ```

1. Vuelva a seleccionar **Ejecutar**. La llamada se sigue realizando correctamente, pero esta vez no se devuelve ningún valor, ya que la publicación ya se había eliminado en Amazon DynamoDB.

   ```
   { "data": { "deletePost": null } }
   ```

## Configuración de un solucionador allPost (Scan de Amazon DynamoDB)
<a name="configure-allpost"></a>

Hasta ahora, la API solo es útil si conoce el `id` de cada publicación que desea ver. Añada un nuevo solucionador que devuelva todas las publicaciones en la tabla.

**Para añadir su mutación**

1. En su API, seleccione la pestaña **Esquema**.

1. En el panel **Schema (Esquema)** modifique el tipo `Query` para agregar una nueva consulta `allPost`, de este modo:

   ```
   type Query {
       allPost(limit: Int, nextToken: String): PaginatedPosts!
       getPost(id: ID): Post
   }
   ```

1. Añada un nuevo tipo `PaginationPosts`:

   ```
   type PaginatedPosts {
       posts: [Post!]!
       nextToken: String
   }
   ```

1. Elija **Save Schema (Guardar esquema)**.

1. En el panel **Solucionadores** de la derecha, busque el campo `allPost` recién creado en el tipo `Query` y, a continuación, seleccione **Asociar**. Cree un nuevo solucionador con el siguiente código:

   ```
   import * as ddb from '@aws-appsync/utils/dynamodb';
   
   export function request(ctx) {
     const { limit = 20, nextToken } = ctx.arguments;
     return ddb.scan({ limit, nextToken });
   }
   
   export function response(ctx) {
     const { items: posts = [], nextToken } = ctx.result;
     return { posts, nextToken };
   }
   ```

   El controlador de solicitudes de este solucionador espera dos argumentos opcionales: 
   + `limit`: especifica el número máximo de elementos que se devolverán en una sola llamada.
   + `nextToken`: se usa para recuperar el siguiente conjunto de resultados (mostraremos de dónde proviene el valor de `nextToken` más adelante).

1. Guarde los cambios realizados en el solucionador.

Si desea más información sobre la solicitud `Scan`, consulte la documentación de referencia de [Scan](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-scan).

### Llame a la API para escanear todas las publicaciones
<a name="call-api-scan"></a>

Ahora que el solucionador está configurado, AWS AppSync sabe cómo convertir una `allPost` consulta entrante en una operación de Amazon `Scan` DynamoDB. Ahora puede recorrer la tabla para obtener todas las publicaciones. Sin embargo, antes de probarlo, debe rellenar la tabla datos, ya que hemos eliminado las publicaciones con las que hemos estado trabajado hasta ahora.

**Para añadir y consultar datos**

1. En su API, seleccione la pestaña **Consultas**.

1. En el panel **Consultas**, pegue la mutación siguiente:

   ```
   mutation addPost {
     post1: addPost(id:1 author: "AUTHORNAME" title: "A series of posts, Volume 1" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title }
     post2: addPost(id:2 author: "AUTHORNAME" title: "A series of posts, Volume 2" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title }
     post3: addPost(id:3 author: "AUTHORNAME" title: "A series of posts, Volume 3" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title }
     post4: addPost(id:4 author: "AUTHORNAME" title: "A series of posts, Volume 4" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title }
     post5: addPost(id:5 author: "AUTHORNAME" title: "A series of posts, Volume 5" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title }
     post6: addPost(id:6 author: "AUTHORNAME" title: "A series of posts, Volume 6" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title }
     post7: addPost(id:7 author: "AUTHORNAME" title: "A series of posts, Volume 7" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title }
     post8: addPost(id:8 author: "AUTHORNAME" title: "A series of posts, Volume 8" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title }
     post9: addPost(id:9 author: "AUTHORNAME" title: "A series of posts, Volume 9" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title }
   }
   ```

1. Seleccione **Ejecutar** (el botón de reproducción naranja). 

1. Ahora recorreremos la tabla obteniendo cinco resultados cada vez. En el panel **Consultas**, añada la siguiente consulta:

   ```
   query allPost {
     allPost(limit: 5) {
       posts {
         id
         title
       }
       nextToken
     }
   }
   ```

1. Seleccione **Ejecutar** (el botón de reproducción naranja) y, a continuación, elija `allPost`.

   Las cinco primeras publicaciones deben aparecer en el panel **Resultados** a la derecha del panel **Consultas**. Debería parecerse a lo que sigue:

   ```
   {
     "data": {
       "allPost": {
         "posts": [
           {
             "id": "5",
             "title": "A series of posts, Volume 5"
           },
           {
             "id": "1",
             "title": "A series of posts, Volume 1"
           },
           {
             "id": "6",
             "title": "A series of posts, Volume 6"
           },
           {
             "id": "9",
             "title": "A series of posts, Volume 9"
           },
           {
             "id": "7",
             "title": "A series of posts, Volume 7"
           }
         ],
         "nextToken": "<token>"
       }
     }
   }
   ```

1. Ha recibido cinco resultados y un `nextToken`, que permite obtener el siguiente conjunto de resultados. Actualice la consulta `allPost` para incluir el valor de `nextToken` del conjunto de resultados anterior: 

   ```
   query allPost {
     allPost(
       limit: 5
       nextToken: "<token>"
     ) {
       posts {
         id
         author
       }
       nextToken
     }
   }
   ```

1. Seleccione **Ejecutar** (el botón de reproducción naranja) y, a continuación, elija `allPost`.

   Las cuatro publicaciones restantes deben aparecer en el panel **Resultados** a la derecha del panel **Consultas**. No hay ningún `nextToken` en este conjunto, porque ya ha recorrido las nueve publicaciones y no quedan más. Debería parecerse a lo que sigue:

   ```
   {
     "data": {
       "allPost": {
         "posts": [
           {
             "id": "2",
             "title": "A series of posts, Volume 2"
           },
           {
             "id": "3",
             "title": "A series of posts, Volume 3"
           },
           {
             "id": "4",
             "title": "A series of posts, Volume 4"
           },
           {
             "id": "8",
             "title": "A series of posts, Volume 8"
           }
         ],
         "nextToken": null
       }
     }
   }
   ```

## Configuración de un solucionador de allPostsBy autores (Amazon DynamoDB Query)
<a name="configure-query"></a>

Además de escanear Amazon DynamoDB para obtener todas las publicaciones, también puede consultar Amazon DynamoDB para obtener las publicaciones creadas por un autor determinado. La tabla de Amazon DynamoDB que creó anteriormente ya tiene un `GlobalSecondaryIndex` llamado `author-index` que puede utilizar con una operación `Query` de Amazon DynamoDB para recuperar todas las publicaciones creadas por un autor específico.

**Para añadir su consulta**

1. En su API, seleccione la pestaña **Esquema**.

1. En el panel **Schema (Esquema)** modifique el tipo `Query` para agregar una nueva consulta `allPostsByAuthor`, de este modo:

   ```
   type Query {
       allPostsByAuthor(author: String!, limit: Int, nextToken: String): PaginatedPosts!
       allPost(limit: Int, nextToken: String): PaginatedPosts!
       getPost(id: ID): Post
   }
   ```

   Tenga en cuenta que aquí vuelve a usarse el tipo `PaginatedPosts` que empleamos con la consulta `allPost`.

1. Elija **Save Schema (Guardar esquema)**.

1. En el panel **Solucionadores** de la derecha, busque el campo `allPostsByAuthor` recién creado en el tipo `Query` y, a continuación, seleccione **Asociar**. Cree un solucionador con el siguiente fragmento:

   ```
   import * as ddb from '@aws-appsync/utils/dynamodb';
   
   export function request(ctx) {
     const { limit = 20, nextToken, author } = ctx.arguments;
     return ddb.query({
       index: 'author-index',
       query: { author: { eq: author } },
       limit,
       nextToken,
     });
   }
   
   export function response(ctx) {
     const { items: posts = [], nextToken } = ctx.result;
     return { posts, nextToken };
   }
   ```

   Al igual que el solucionador `allPost`, este solucionador tiene dos argumentos opcionales:
   + `limit`: especifica el número máximo de elementos que se devolverán en una sola llamada.
   + `nextToken`: recupera el siguiente conjunto de resultados (el valor de `nextToken` puede obtenerse de una llamada anterior).

1. Guarde los cambios realizados en el solucionador.

Si desea más información sobre la solicitud `Query`, consulte la documentación de referencia de [Query](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-query).

### Llame a la API para consultar todas las publicaciones de un autor
<a name="call-api-query"></a>

Ahora que el solucionador está configurado, AWS AppSync sabe cómo traducir una `allPostsByAuthor` mutación entrante en una operación de `Query` DynamoDB contra el índice. `author-index` Ahora puede consultar la tabla para recuperar todas las publicaciones de un autor determinado.

Sin embargo, antes de hacerlo, debemos rellenar la tabla con algunas publicaciones más, ya que por el momento todas son del mismo autor.

**Para añadir datos y consultas**

1. En su API, seleccione la pestaña **Consultas**.

1. En el panel **Consultas**, pegue la mutación siguiente:

   ```
   mutation addPost {
     post1: addPost(id:10 author: "Nadia" title: "The cutest dog in the world" content: "So cute. So very, very cute." url: "https://aws.amazon.com/appsync/" ) { author, title }
     post2: addPost(id:11 author: "Nadia" title: "Did you know...?" content: "AppSync works offline?" url: "https://aws.amazon.com/appsync/" ) { author, title }
     post3: addPost(id:12 author: "Steve" title: "I like GraphQL" content: "It's great" url: "https://aws.amazon.com/appsync/" ) { author, title }
   }
   ```

1. Seleccione **Ejecutar** (el botón de reproducción naranja) y, a continuación, elija `addPost`.

1. Ahora consultaremos la tabla para que devuelva todas las publicaciones creadas por `Nadia`. En el panel **Consultas**, añada la siguiente consulta: 

   ```
   query allPostsByAuthor {
     allPostsByAuthor(author: "Nadia") {
       posts {
         id
         title
       }
       nextToken
     }
   }
   ```

1. Seleccione **Ejecutar** (el botón de reproducción naranja) y, a continuación, elija `allPostsByAuthor`. Todas las publicaciones creadas por `Nadia` deben aparecer en el panel **Resultados** a la derecha del panel **Consultas**. Debería parecerse a lo que sigue:

   ```
   {
     "data": {
       "allPostsByAuthor": {
         "posts": [
           {
             "id": "10",
             "title": "The cutest dog in the world"
           },
           {
             "id": "11",
             "title": "Did you know...?"
           }
         ],
         "nextToken": null
       }
     }
   }
   ```

1. La paginación funciona con `Query` de igual modo que con `Scan`. Por ejemplo, vamos a buscar todas las publicaciones de `AUTHORNAME` y obtener cinco cada vez.

1. En el panel **Consultas**, añada la siguiente consulta: 

   ```
   query allPostsByAuthor {
     allPostsByAuthor(
       author: "AUTHORNAME"
       limit: 5
     ) {
       posts {
         id
         title
       }
       nextToken
     }
   }
   ```

1. Seleccione **Ejecutar** (el botón de reproducción naranja) y, a continuación, elija `allPostsByAuthor`. Todas las publicaciones creadas por `AUTHORNAME` deben aparecer en el panel **Resultados** a la derecha del panel **Consultas**. Debería parecerse a lo que sigue:

   ```
   {
     "data": {
       "allPostsByAuthor": {
         "posts": [
           {
             "id": "6",
             "title": "A series of posts, Volume 6"
           },
           {
             "id": "4",
             "title": "A series of posts, Volume 4"
           },
           {
             "id": "2",
             "title": "A series of posts, Volume 2"
           },
           {
             "id": "7",
             "title": "A series of posts, Volume 7"
           },
           {
             "id": "1",
             "title": "A series of posts, Volume 1"
           }
         ],
         "nextToken": "<token>"
       }
     }
   }
   ```

1. Actualice el argumento `nextToken` con el valor devuelto en la consulta anterior, de este modo:

   ```
   query allPostsByAuthor {
     allPostsByAuthor(
       author: "AUTHORNAME"
       limit: 5
       nextToken: "<token>"
     ) {
       posts {
         id
         title
       }
       nextToken
     }
   }
   ```

1. Seleccione **Ejecutar** (el botón de reproducción naranja) y, a continuación, elija `allPostsByAuthor`. Las demás publicaciones creadas por `AUTHORNAME` deben aparecer en el panel **Resultados** a la derecha del panel **Consultas**. Debería parecerse a lo que sigue:

   ```
   {
     "data": {
       "allPostsByAuthor": {
         "posts": [
           {
             "id": "8",
             "title": "A series of posts, Volume 8"
           },
           {
             "id": "5",
             "title": "A series of posts, Volume 5"
           },
           {
             "id": "3",
             "title": "A series of posts, Volume 3"
           },
           {
             "id": "9",
             "title": "A series of posts, Volume 9"
           }
         ],
         "nextToken": null
       }
     }
   }
   ```

## Uso de conjuntos
<a name="using-sets"></a>

Hasta este punto, el `Post` tipo era un objeto plano key/value . El solucionador también permite modelar objetos complejos, como conjuntos, listas y mapas. Vamos a actualizar el tipo `Post` para incluir etiquetas. Una publicación puede tener cero o más etiquetas, que se almacenan en DynamoDB como un conjunto de cadenas. También configuraremos algunas mutaciones para añadir y eliminar etiquetas, y una nueva consulta para buscar las publicaciones con una etiqueta concreta.

**Para configurar sus datos**

1. En su API, seleccione la pestaña **Esquema**. 

1. En el panel **Schema (Esquema)** modifique el tipo `Post` para agregar un nuevo campo `tags`, de este modo:

   ```
   type Post {
     id: ID!
     author: String
     title: String
     content: String
     url: String
     ups: Int!
     downs: Int!
     version: Int!
     tags: [String!]
   }
   ```

1. En el panel **Schema (Esquema)** modifique el tipo `Query` para agregar una nueva consulta `allPostsByTag`, de este modo:

   ```
   type Query {
     allPostsByTag(tag: String!, limit: Int, nextToken: String): PaginatedPosts!
     allPostsByAuthor(author: String!, limit: Int, nextToken: String): PaginatedPosts!
     allPost(limit: Int, nextToken: String): PaginatedPosts!
     getPost(id: ID): Post
   }
   ```

1. En el panel **Schema (Schema)**, modifique el tipo `Mutation` para agregar las nuevas mutaciones `addTag` y `removeTag`, de este modo:

   ```
   type Mutation {
     addTag(id: ID!, tag: String!): Post
     removeTag(id: ID!, tag: String!): Post
     deletePost(id: ID!, expectedVersion: Int): Post
     upvotePost(id: ID!): Post
     downvotePost(id: ID!): Post
     updatePost(
       id: ID!,
       author: String,
       title: String,
       content: String,
       url: String,
       expectedVersion: Int!
     ): Post
     addPost(
       author: String!,
       title: String!,
       content: String!,
       url: String!
     ): Post!
   }
   ```

1. Elija **Save Schema (Guardar esquema)**.

1. En el panel **Solucionadores** de la derecha, busque el campo `allPostsByTag` recién creado en el tipo `Query` y, a continuación, seleccione **Asociar**. Cree su solucionador con el siguiente fragmento:

   ```
   import * as ddb from '@aws-appsync/utils/dynamodb';
   
   export function request(ctx) {
     const { limit = 20, nextToken, tag } = ctx.arguments;
     return ddb.scan({ limit, nextToken, filter: { tags: { contains: tag } } });
   }
   
   export function response(ctx) {
     const { items: posts = [], nextToken } = ctx.result;
     return { posts, nextToken };
   }
   ```

1. Guarde los cambios que haya realizado en el solucionador.

1. Ahora, haga lo mismo con el campo `addTag` de `Mutation` usando el siguiente fragmento:
**nota**  
Aunque las utilidades de DynamoDB actualmente no admiten operaciones de conjuntos, puede interactuar con los conjuntos creando la solicitud usted mismo.

   ```
   import { util } from '@aws-appsync/utils'
   
   export function request(ctx) {
   	const { id, tag } = ctx.arguments
   	const expressionValues = util.dynamodb.toMapValues({ ':plusOne': 1 })
   	expressionValues[':tags'] = util.dynamodb.toStringSet([tag])
   
   	return {
   		operation: 'UpdateItem',
   		key: util.dynamodb.toMapValues({ id }),
   		update: {
   			expression: `ADD tags :tags, version :plusOne`,
   			expressionValues,
   		},
   	}
   }
   
   export const response = (ctx) => ctx.result
   ```

1. Guarde los cambios realizados en el solucionador.

1. Repita esta operación una vez más para el campo `removeTag` de `Mutation` utilizando el siguiente fragmento:

   ```
   import { util } from '@aws-appsync/utils';
   	
   export function request(ctx) {
   	  const { id, tag } = ctx.arguments;
   	  const expressionValues = util.dynamodb.toMapValues({ ':plusOne': 1 });
   	  expressionValues[':tags'] = util.dynamodb.toStringSet([tag]);
   	
   	  return {
   	    operation: 'UpdateItem',
   	    key: util.dynamodb.toMapValues({ id }),
   	    update: {
   	      expression: `DELETE tags :tags ADD version :plusOne`,
   	      expressionValues,
   	    },
   	  };
   	}
   	
   	export const response = (ctx) => ctx.resultexport
   ```

1. Guarde los cambios realizados en el solucionador.

### Llame a la API para trabajar con etiquetas
<a name="call-api-tags"></a>

Ahora que ha configurado los resolutores, AWS AppSync sabe cómo traducir las solicitudes entrantes `addTag` y las `allPostsByTag` solicitudes a `UpdateItem` DynamoDB `Scan` y a las operaciones. `removeTag` Para probarlo, vamos a seleccionar una de las publicaciones que ha creado anteriormente. Por ejemplo, tomemos una publicación creada por `Nadia`.

**Para usar etiquetas**

1. En su API, seleccione la pestaña **Consultas**.

1. En el panel **Consultas**, añada la siguiente consulta:

   ```
   query allPostsByAuthor {
     allPostsByAuthor(
       author: "Nadia"
     ) {
       posts {
         id
         title
       }
       nextToken
     }
   }
   ```

1. Seleccione **Ejecutar** (el botón de reproducción naranja) y, a continuación, elija `allPostsByAuthor`.

1. Todas las publicaciones de Nadia deben aparecer en el panel **Resultados** a la derecha del panel **Consultas**. Debería parecerse a lo que sigue:

   ```
   {
     "data": {
       "allPostsByAuthor": {
         "posts": [
           {
             "id": "10",
             "title": "The cutest dog in the world"
           },
           {
             "id": "11",
             "title": "Did you known...?"
           }
         ],
         "nextToken": null
       }
     }
   }
   ```

1. Usemos el que tiene el título *The cutest dog in the world*. Registre su `id`, ya que vamos a utilizarlo más adelante. Ahora probemos a añadirle la etiqueta `dog`.

1. En el panel **Consultas**, pegue la mutación siguiente. También tendrá que actualizar el argumento `id` con el valor que anotó anteriormente.

   ```
   mutation addTag {
     addTag(id:10 tag: "dog") {
       id
       title
       tags
     }
   }
   ```

1. Seleccione **Ejecutar** (el botón de reproducción naranja) y, a continuación, elija `addTag`. La publicación se actualiza con la nueva etiqueta:

   ```
   {
     "data": {
       "addTag": {
         "id": "10",
         "title": "The cutest dog in the world",
         "tags": [
           "dog"
         ]
       }
     }
   }
   ```

1. Puede añadir más etiquetas. Actualice la mutación para cambiar el argumento `tag` a `puppy`:

   ```
   mutation addTag {
     addTag(id:10 tag: "puppy") {
       id
       title
       tags
     }
   }
   ```

1. Seleccione **Ejecutar** (el botón de reproducción naranja) y, a continuación, elija `addTag`. La publicación se actualiza con la nueva etiqueta:

   ```
   {
     "data": {
       "addTag": {
         "id": "10",
         "title": "The cutest dog in the world",
         "tags": [
           "dog",
           "puppy"
         ]
       }
     }
   }
   ```

1. También puede eliminar etiquetas. En el panel **Consultas**, pegue la mutación siguiente. También tendrá que actualizar el argumento `id` con el valor que anotó anteriormente:

   ```
   mutation removeTag {
     removeTag(id:10 tag: "puppy") {
       id
       title
       tags
     }
   }
   ```

1. Seleccione **Ejecutar** (el botón de reproducción naranja) y, a continuación, elija `removeTag`. La publicación se actualiza y la etiqueta `puppy` se elimina.

   ```
   {
     "data": {
       "addTag": {
         "id": "10",
         "title": "The cutest dog in the world",
         "tags": [
           "dog"
         ]
       }
     }
   }
   ```

1. También puede buscar todas las publicaciones que tengan una etiqueta. En el panel **Consultas**, añada la siguiente consulta: 

   ```
   query allPostsByTag {
     allPostsByTag(tag: "dog") {
       posts {
         id
         title
         tags
       }
       nextToken
     }
   }
   ```

1. Seleccione **Ejecutar** (el botón de reproducción naranja) y, a continuación, elija `allPostsByTag`. Se devolverán todas las publicaciones que tenga la etiqueta `dog`, de este modo:

   ```
   {
     "data": {
       "allPostsByTag": {
         "posts": [
           {
             "id": "10",
             "title": "The cutest dog in the world",
             "tags": [
               "dog",
               "puppy"
             ]
           }
         ],
         "nextToken": null
       }
     }
   }
   ```

## Conclusión
<a name="conclusion-dynamodb-tutorial-js"></a>

En este tutorial ha creado una API que nos permite manipular objetos `Post` en DynamoDB mediante AWS AppSync y GraphQL. 

Para limpiar, puedes eliminar la API de AWS AppSync GraphQL de la consola. 

Para eliminar el rol asociado a la tabla de DynamoDB, seleccione el origen de datos en la tabla **Orígenes de datos** y haga clic en **Editar**. Anote el valor del rol en **Crear o usar un rol existente**. Vaya a la consola de IAM para eliminar el rol.

Para eliminar la tabla de DynamoDB, haga clic en el nombre de la tabla en la lista de orígenes de datos. Esto le llevará a la consola de DynamoDB, donde podrá eliminar la tabla. 

# Uso de AWS Lambda resolutores en AWS AppSync
<a name="tutorial-lambda-resolvers-js"></a>

Puedes usar AWS Lambda with AWS AppSync para resolver cualquier campo de GraphQL. Por ejemplo, una consulta de GraphQL podría enviar una llamada a una instancia de Amazon Relational Database Service (Amazon RDS), y una mutación de GraphQL podría escribir en un flujo de Amazon Kinesis. En esta sección, veremos cómo puede escribir una función de lambda que ejecute la lógica de negocio en función de la invocación de una operación de campo de GraphQL.

## Herramientas eléctricas para AWS Lambda
<a name="powertools-graphql"></a>

El controlador de eventos Powertools for AWS Lambda GraphQL simplifica el enrutamiento y el procesamiento de los eventos de GraphQL en las funciones de Lambda. Está disponible para Python y TypeScript. Obtenga más información sobre el controlador de eventos de API de GraphQL en la documentación de Powertools for AWS Lambda . Consulte las siguientes referencias.
+ [Powertools para el controlador de eventos AWS Lambda GraphQL (Python)](https://docs.aws.amazon.com/powertools/python/latest/core/event_handler/appsync/)
+ [Powertools para el controlador de eventos AWS Lambda GraphQL (Typescript)](https://docs.aws.amazon.com/powertools/typescript/latest/features/event-handler/appsync-graphql/) 

## Crear una función de Lambda
<a name="create-a-lam-function-js"></a>

En el siguiente ejemplo se muestra una función de lambda escrita en `Node.js` (tiempo de ejecución: Node.js 18.x) que realiza distintas operaciones en publicaciones de blogs como parte de una aplicación de publicaciones en blogs. Tenga en cuenta que el código debe guardarse en un nombre de archivo con la extensión .mis.

```
export const handler = async (event) => {
console.log('Received event {}', JSON.stringify(event, 3))

  const posts = {
1: { id: '1', title: 'First book', author: 'Author1', url: 'https://amazon.com/', content: 'SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1', ups: '100', downs: '10', },
    2: { id: '2', title: 'Second book', author: 'Author2', url: 'https://amazon.com', content: 'SAMPLE TEXT AUTHOR 2 SAMPLE TEXT AUTHOR 2 SAMPLE TEXT', ups: '100', downs: '10', },
    3: { id: '3', title: 'Third book', author: 'Author3', url: null, content: null, ups: null, downs: null },
    4: { id: '4', title: 'Fourth book', author: 'Author4', url: 'https://www.amazon.com/', content: 'SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4', ups: '1000', downs: '0', },
    5: { id: '5', title: 'Fifth book', author: 'Author5', url: 'https://www.amazon.com/', content: 'SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT', ups: '50', downs: '0', },
  }

  const relatedPosts = {
1: [posts['4']],
    2: [posts['3'], posts['5']],
    3: [posts['2'], posts['1']],
    4: [posts['2'], posts['1']],
    5: [],
  }

  console.log('Got an Invoke Request.')
  let result
  switch (event.field) {
case 'getPost':
      return posts[event.arguments.id]
    case 'allPosts':
      return Object.values(posts)
    case 'addPost':
      // return the arguments back
return event.arguments
    case 'addPostErrorWithData':
      result = posts[event.arguments.id]
      // attached additional error information to the post
      result.errorMessage = 'Error with the mutation, data has changed'
      result.errorType = 'MUTATION_ERROR'
return result
    case 'relatedPosts':
      return relatedPosts[event.source.id]
    default:
      throw new Error('Unknown field, unable to resolve ' + event.field)
  }
}
```

Esta función de Lambda recupera una publicación por identificador, añade una publicación, recupera una lista de publicaciones y recupera publicaciones relacionadas para una publicación determinada. 

**nota**  
La función de Lambda utiliza la instrucción `switch` en `event.field` para determinar qué campo se está resolviendo en ese momento.

Cree esta función Lambda mediante la consola de AWS administración.

## Configure un origen de datos para Lambda
<a name="configure-data-source-for-lamlong-js"></a>

Una vez creada la función de Lambda, vaya a la API de GraphQL en la consola de AWS AppSync y elija la pestaña **Orígenes de datos**.

Elija **Crear origen de datos**, introduzca un **Nombre de origen de datos** fácil de recordar (por ejemplo, **Lambda**) y, a continuación, en **Tipo de origen de datos**, elija **Función de AWS Lambda .** En **Región**, elija la misma región que en su función. En **ARN de función**, elija el nombre de recurso de Amazon (ARN) para la función de Lambda.

Tras elegir la función Lambda, puede crear una nueva función (de IAM) AWS Identity and Access Management (para la que se AWS AppSync asignen los permisos adecuados) o elegir una función existente que tenga la siguiente política en línea:

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

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "lambda:InvokeFunction"
            ],
            "Resource": "arn:aws:lambda:us-east-1:111122223333:function:LAMBDA_FUNCTION"
        }
    ]
}
```

------

También debe establecer una relación de confianza con el rol de IAM de AWS AppSync la siguiente manera:

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

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "appsync.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
```

------

## Cree un esquema de GraphQL
<a name="creating-a-graphql-schema-js"></a>

Ahora que el origen de datos está conectado a la función de Lambda, cree un esquema de GraphQL.

En el editor de esquemas de la AWS AppSync consola, asegúrese de que el esquema coincide con el siguiente esquema:

```
schema {
    query: Query
    mutation: Mutation
}
type Query {
    getPost(id:ID!): Post
    allPosts: [Post]
}
type Mutation {
    addPost(id: ID!, author: String!, title: String, content: String, url: String): Post!
}
type Post {
    id: ID!
    author: String!
    title: String
    content: String
    url: String
    ups: Int
    downs: Int
    relatedPosts: [Post]
}
```

## Configure solucionadores
<a name="configuring-resolvers-js"></a>

Ahora que ha registrado un origen de datos de Lambda y un esquema de GraphQL válido, puede conectar sus campos de GraphQL al origen de datos de Lambda utilizando solucionadores.

Creará un solucionador que utilice el tiempo de ejecución AWS AppSync JavaScript (`APPSYNC_JS`) e interactuará con las funciones de Lambda. Para obtener más información sobre cómo escribir AWS AppSync resolutores y funciones con ellos JavaScript, consulte las [características del JavaScript tiempo de ejecución para resolutores](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-util-reference-js.html) y funciones.

Para obtener más información sobre las plantillas de mapeo de Lambda, consulte la [referencia de funciones JavaScript de resolución para Lambda](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-reference-lambda-js.html).

En este paso, debe asociar un solucionador a la función de Lambda para los siguientes campos: `getPost(id:ID!): Post`, `allPosts: [Post]`, `addPost(id: ID!, author: String!, title: String, content: String, url: String): Post!` y `Post.relatedPosts: [Post]`. En el editor de **esquemas** de la AWS AppSync consola, en el panel **Resolvers**, seleccione **Adjuntar** junto al campo. `getPost(id:ID!): Post` Elija el origen de datos de Lambda. A continuación, introduzca el siguiente código:

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

export function request(ctx) {
  const {source, args} = ctx
  return {
    operation: 'Invoke',
    payload: { field: ctx.info.fieldName, arguments: args, source },
  };
}

export function response(ctx) {
  return ctx.result;
}
```

Este código de solucionador pasa el nombre del campo, la lista de argumentos y el contexto del objeto de origen a la función de Lambda cuando la invoca. Seleccione **Save**.

Acaba de asociar su primer solucionador. Repita esta operación para el resto de los campos. 

## Pruebe la API de GraphQL
<a name="testing-your-graphql-api-js"></a>

Ahora que la función de Lambda está conectada a los solucionadores de GraphQL, puede ejecutar algunas mutaciones y consultas con la consola o una aplicación cliente.

En la parte izquierda de la AWS AppSync consola, selecciona **Consultas** y, a continuación, pega el siguiente código:

### Mutación addPost
<a name="addpost-mutation-js"></a>

```
mutation AddPost {
    addPost(
        id: 6
        author: "Author6"
        title: "Sixth book"
        url: "https://www.amazon.com/"
        content: "This is the book is a tutorial for using GraphQL with AWS AppSync."
    ) {
        id
        author
        title
        content
        url
        ups
        downs
    }
}
```

### Consulta getPost
<a name="getpost-query-js"></a>

```
query GetPost {
    getPost(id: "2") {
        id
        author
        title
        content
        url
        ups
        downs
    }
}
```

### Consulta allPosts
<a name="allposts-query-js"></a>

```
query AllPosts {
    allPosts {
        id
        author
        title
        content
        url
        ups
        downs
        relatedPosts {
            id
            title
        }
    }
}
```

## Devolución de errores
<a name="returning-errors-js"></a>

Cualquier resolución de campo dada puede producir un error. Con AWS AppSync, puede generar errores de las siguientes fuentes:
+ Controlador de respuestas del solucionador
+ Función de Lambda

### Desde el controlador de respuestas del solucionador
<a name="from-the-resolver-response-handler-js"></a>

Para generar errores intencionados, puede utilizar el método de la utilidad `util.error`. Toma como argumento un `errorMessage`, un `errorType` y un valor opcional de `data`. El argumento `data` es útil para devolver datos adicionales al cliente cuando se produce un error. El objeto `data` se añade a `errors` en la respuesta final de GraphQL.

En el ejemplo siguiente se muestra cómo utilizarlo en el controlador de respuestas del solucionador de `Post.relatedPosts: [Post]`.

```
// the Post.relatedPosts response handler
export function response(ctx) {
    util.error("Failed to fetch relatedPosts", "LambdaFailure", ctx.result)
    return ctx.result;
}
```

Así se obtiene una respuesta de GraphQL similar a la siguiente:

```
{
    "data": {
        "allPosts": [
            {
                "id": "2",
                "title": "Second book",
                "relatedPosts": null
            },
            ...
        ]
    },
    "errors": [
        {
            "path": [
                "allPosts",
                0,
                "relatedPosts"
            ],
            "errorType": "LambdaFailure",
            "locations": [
                {
                    "line": 5,
                    "column": 5
                }
            ],
            "message": "Failed to fetch relatedPosts",
            "data": [
                {
                  "id": "2",
                  "title": "Second book"
                },
                {
                  "id": "1",
                  "title": "First book"
                }
            ]
        }
    ]
}
```

Donde `allPosts[0].relatedPosts` es *null* debido al error y `errorMessage`, `errorType` y `data` se incluyen en el objeto `data.errors[0]`.

### Desde la función de Lambda
<a name="from-the-lam-function-js"></a>

AWS AppSync también entiende los errores que arroja la función Lambda. El modelo de programación de Lambda permite generar errores *gestionados*. Si la función Lambda arroja un error, AWS AppSync no resuelve el campo actual. La respuesta solo incluirá el mensaje de error que devuelva Lambda. Actualmente no es posible devolver datos adicionales al cliente generando un error desde la función de Lambda. 

**nota**  
Si la función Lambda genera un error *no controlado*, AWS AppSync utiliza el mensaje de error que Lambda estableció.

La siguiente función de Lambda genera un error:

```
export const handler = async (event) => {
  console.log('Received event {}', JSON.stringify(event, 3))
  throw new Error('I always fail.')
}
```

El error se recibe en el controlador de respuestas. Puede devolverlo en la respuesta de GraphQL añadiendo el error a la respuesta con `util.appendError`. Para ello, cambia el controlador de respuesta de AWS AppSync la función por el siguiente:

```
// the lambdaInvoke response handler
export function response(ctx) {
  const { error, result } = ctx;
  if (error) {
    util.appendError(error.message, error.type, result);
  }
  return result;
}
```

Así se obtiene una respuesta de GraphQL similar a la siguiente:

```
{
  "data": {
    "allPosts": null
  },
  "errors": [
    {
      "path": [
        "allPosts"
      ],
      "data": null,
      "errorType": "Lambda:Unhandled",
      "errorInfo": null,
      "locations": [
        {
          "line": 2,
          "column": 3,
          "sourceName": null
        }
      ],
      "message": "I fail. always"
    }
  ]
}
```

## Caso de uso avanzado: agrupación en lotes
<a name="advanced-use-case-batching-js"></a>

La función de Lambda de este ejemplo tiene un campo `relatedPosts` que devuelve una lista de publicaciones relacionadas para una publicación determinada. En las consultas del ejemplo, la invocación al campo `allPosts` desde la función de Lambda devuelve cinco publicaciones. Dado que hemos especificado que también queremos resolver `relatedPosts` para cada publicación obtenida, la operación del campo `relatedPosts` se invoca cinco veces.

```
query {
    allPosts {   // 1 Lambda invocation - yields 5 Posts
        id
        author
        title
        content
        url
        ups
        downs
        relatedPosts {   // 5 Lambda invocations - each yields 5 posts
            id
            title
        }
    }
}
```

Aunque no parezca mucho en este ejemplo concreto, esta sobrecarga compuesta puede perjudicar rápidamente a la aplicación.

Si quisiéramos obtener `relatedPosts` otra vez para todos los elementos de `Posts` en la misma consulta, el número de invocaciones aumentaría exponencialmente.

```
query {
    allPosts {   // 1 Lambda invocation - yields 5 Posts
        id
        author
        title
        content
        url
        ups
        downs
        relatedPosts {   // 5 Lambda invocations - each yield 5 posts = 5 x 5 Posts
            id
            title
            relatedPosts {  // 5 x 5 Lambda invocations - each yield 5 posts = 25 x 5 Posts
                id
                title
                author
            }
        }
    }
}
```

En esta consulta relativamente simple, AWS AppSync invocaría la función Lambda 1 \$1 5 \$1 25 = 31 veces.

Se trata de una situación bastante habitual que a menudo se denomina "problema N\$11", (en nuestro caso, N = 5) y puede causar un aumento de la latencia y del costo de la aplicación.

Una forma de solucionarlo es agrupar por lotes las solicitudes de solucionador de campo similares. En este ejemplo, en lugar de hacer que la función de Lambda obtenga una lista de publicaciones relacionadas con una publicación individual determinada, hacemos que obtenga una lista de publicaciones relacionadas con un lote de publicaciones dado.

Para demostrarlo, actualicemos el solucionador para que `relatedPosts` gestione la agrupación en lotes.

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

export function request(ctx) {
  const {source, args} = ctx
  return {
    operation: ctx.info.fieldName === 'relatedPosts' ? 'BatchInvoke' : 'Invoke',
    payload: { field: ctx.info.fieldName, arguments: args, source },
  };
}

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

El código ahora cambia la operación de `Invoke` a `BatchInvoke` cuando el `fieldName` que se está resolviendo es `relatedPosts`. Ahora, habilite la agrupación en lotes en la función en la sección **Configuración de la agrupación en lotes**. Establezca el tamaño máximo de la agrupación en lotes en `5`. Seleccione **Save**.

Con este cambio, al resolver `relatedPosts`, la función de Lambda recibe lo siguiente como entrada:

```
[
    {
        "field": "relatedPosts",
        "source": {
            "id": 1
        }
    },
    {
        "field": "relatedPosts",
        "source": {
            "id": 2
        }
    },
    ...
]
```

Cuando se especifica `BatchInvoke` en la solicitud, la función de Lambda recibe una lista de solicitudes y devuelve una lista de resultados.

En concreto, la lista de resultados debe coincidir con el tamaño y el orden de las entradas de carga útil de la solicitud para que AWS AppSync los resultados coincidan en consecuencia.

En este ejemplo de agrupación en lotes, la función de Lambda devuelve un lote de resultados de este modo:

```
[
    [{"id":"2","title":"Second book"}, {"id":"3","title":"Third book"}],   // relatedPosts for id=1
    [{"id":"3","title":"Third book"}]                                     // relatedPosts for id=2
]
```

Puede actualizar el código de Lambda para gestionar la agrupación en lotes para `relatedPosts`:

```
export const handler = async (event) => {
  console.log('Received event {}', JSON.stringify(event, 3))
  //throw new Error('I fail. always')

  const posts = {
    1: { id: '1', title: 'First book', author: 'Author1', url: 'https://amazon.com/', content: 'SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1', ups: '100', downs: '10', },
    2: { id: '2', title: 'Second book', author: 'Author2', url: 'https://amazon.com', content: 'SAMPLE TEXT AUTHOR 2 SAMPLE TEXT AUTHOR 2 SAMPLE TEXT', ups: '100', downs: '10', },
    3: { id: '3', title: 'Third book', author: 'Author3', url: null, content: null, ups: null, downs: null },
    4: { id: '4', title: 'Fourth book', author: 'Author4', url: 'https://www.amazon.com/', content: 'SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4', ups: '1000', downs: '0', },
    5: { id: '5', title: 'Fifth book', author: 'Author5', url: 'https://www.amazon.com/', content: 'SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT', ups: '50', downs: '0', },
  }

  const relatedPosts = {
    1: [posts['4']],
    2: [posts['3'], posts['5']],
    3: [posts['2'], posts['1']],
    4: [posts['2'], posts['1']],
    5: [],
  }
  
  if (!event.field && event.length){
    console.log(`Got a BatchInvoke Request. The payload has ${event.length} items to resolve.`);
    return event.map(e => relatedPosts[e.source.id])
  }

  console.log('Got an Invoke Request.')
  let result
  switch (event.field) {
    case 'getPost':
      return posts[event.arguments.id]
    case 'allPosts':
      return Object.values(posts)
    case 'addPost':
      // return the arguments back
      return event.arguments
    case 'addPostErrorWithData':
      result = posts[event.arguments.id]
      // attached additional error information to the post
      result.errorMessage = 'Error with the mutation, data has changed'
      result.errorType = 'MUTATION_ERROR'
      return result
    case 'relatedPosts':
      return relatedPosts[event.source.id]
    default:
      throw new Error('Unknown field, unable to resolve ' + event.field)
  }
}
```

### Devolución de errores individuales
<a name="returning-individual-errors-js"></a>

Los ejemplos anteriores muestran que es posible devolver un único error desde la función de Lambda o generar un error desde su controlador de respuestas. En las invocaciones en lotes, la generación de un error desde la función de Lambda marca como fallido todo el lote. Esto puede ser adecuado en situaciones concretas donde se haya producido un error irrecuperable, como, por ejemplo, un error de conexión a un almacén de datos. Sin embargo, en los casos en los que algunos elementos del lote se ejecutan correctamente y otros fallan, es posible devolver tanto los errores como los datos válidos. Dado AWS AppSync que la respuesta del lote requiere que se enumeren los elementos que coincidan con el tamaño original del lote, debe definir una estructura de datos que pueda diferenciar los datos válidos de los errores.

Por ejemplo, si se espera que la función de Lambda devuelva un lote de publicaciones relacionadas, podría optar por devolver una lista de objetos `Response` en la que cada objeto tenga los campos opcionales *data*, *errorMessage* y *errorType*. Si el campo *errorMessage* está presente, significa que se ha producido un error.

El código siguiente muestra cómo podría actualizar la función de Lambda:

```
export const handler = async (event) => {
console.log('Received event {}', JSON.stringify(event, 3))
  // throw new Error('I fail. always')
const posts = {
1: { id: '1', title: 'First book', author: 'Author1', url: 'https://amazon.com/', content: 'SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1', ups: '100', downs: '10', },
    2: { id: '2', title: 'Second book', author: 'Author2', url: 'https://amazon.com', content: 'SAMPLE TEXT AUTHOR 2 SAMPLE TEXT AUTHOR 2 SAMPLE TEXT', ups: '100', downs: '10', },
    3: { id: '3', title: 'Third book', author: 'Author3', url: null, content: null, ups: null, downs: null },
    4: { id: '4', title: 'Fourth book', author: 'Author4', url: 'https://www.amazon.com/', content: 'SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4', ups: '1000', downs: '0', },
    5: { id: '5', title: 'Fifth book', author: 'Author5', url: 'https://www.amazon.com/', content: 'SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT', ups: '50', downs: '0', },
  }

  const relatedPosts = {
1: [posts['4']],
    2: [posts['3'], posts['5']],
    3: [posts['2'], posts['1']],
    4: [posts['2'], posts['1']],
    5: [],
  }
  
  if (!event.field && event.length){
console.log(`Got a BatchInvoke Request. The payload has ${event.length} items to resolve.`);
    return event.map(e => {
// return an error for post 2
if (e.source.id === '2') {
return { 'data': null, 'errorMessage': 'Error Happened', 'errorType': 'ERROR' }
      }
      return {data: relatedPosts[e.source.id]}
      })
  }

  console.log('Got an Invoke Request.')
  let result
  switch (event.field) {
case 'getPost':
      return posts[event.arguments.id]
    case 'allPosts':
      return Object.values(posts)
    case 'addPost':
      // return the arguments back
return event.arguments
    case 'addPostErrorWithData':
      result = posts[event.arguments.id]
      // attached additional error information to the post
      result.errorMessage = 'Error with the mutation, data has changed'
      result.errorType = 'MUTATION_ERROR'
return result
    case 'relatedPosts':
      return relatedPosts[event.source.id]
    default:
      throw new Error('Unknown field, unable to resolve ' + event.field)
  }
}
```

Actualice el código del solucionador `relatedPosts`:

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

export function request(ctx) {
  const {source, args} = ctx
  return {
    operation: ctx.info.fieldName === 'relatedPosts' ? 'BatchInvoke' : 'Invoke',
    payload: { field: ctx.info.fieldName, arguments: args, source },
  };
}

export function response(ctx) {
  const { error, result } = ctx;
  if (error) {
    util.appendError(error.message, error.type, result);
  } else if (result.errorMessage) {
    util.appendError(result.errorMessage, result.errorType, result.data)
  } else if (ctx.info.fieldName === 'relatedPosts') {
      return result.data
  } else {
      return result
  }
}
```

El controlador de respuestas ahora comprueba los errores devueltos por la función de Lambda en las operaciones `Invoke`, comprueba los errores devueltos para elementos individuales de las operaciones `BatchInvoke` y, finalmente, comprueba el `fieldName`. Para `relatedPosts`, la función devuelve `result.data`. Para el resto de los campos, la función simplemente devuelve `result`. Por ejemplo, veamos la siguiente consulta:

```
query AllPosts {
  allPosts {
    id
    title
    content
    url
    ups
    downs
    relatedPosts {
      id
    }
    author
  }
}
```

Esta consulta devuelve una respuesta de GraphQL similar a la siguiente:

```
{
  "data": {
    "allPosts": [
      {
        "id": "1",
        "relatedPosts": [
          {
            "id": "4"
          }
        ]
      },
      {
        "id": "2",
        "relatedPosts": null
      },
      {
        "id": "3",
        "relatedPosts": [
          {
            "id": "2"
          },
          {
            "id": "1"
          }
        ]
      },
      {
        "id": "4",
        "relatedPosts": [
          {
            "id": "2"
          },
          {
            "id": "1"
          }
        ]
      },
      {
        "id": "5",
        "relatedPosts": []
      }
    ]
  },
  "errors": [
    {
      "path": [
        "allPosts",
        1,
        "relatedPosts"
      ],
      "data": null,
      "errorType": "ERROR",
      "errorInfo": null,
      "locations": [
        {
          "line": 4,
          "column": 5,
          "sourceName": null
        }
      ],
      "message": "Error Happened"
    }
  ]
}
```

### Configuración del tamaño máximo de agrupación en lotes
<a name="configure-max-batch-size-js"></a>

Para configurar el tamaño máximo de procesamiento por lotes en una resolución, utilice el siguiente comando en AWS Command Line Interface (AWS CLI):

```
$ aws appsync create-resolver --api-id <api-id> --type-name Query --field-name relatedPosts \
 --code "<code-goes-here>" \
 --runtime name=APPSYNC_JS,runtimeVersion=1.0.0 \
 --data-source-name "<lambda-datasource>" \ 
 --max-batch-size X
```

**nota**  
Al proporcionar una plantilla de mapeo de solicitudes, debe usar la operación `BatchInvoke` para usar la agrupación en lotes.

# Uso de solucionadores locales en AWS AppSync
<a name="tutorial-local-resolvers-js"></a>

AWS AppSync le permite utilizar orígenes de datos compatibles (AWS Lambda, Amazon DynamoDB o Amazon OpenSearch Service) para realizar diversas operaciones. Sin embargo, en determinados casos, es posible que no sea necesario realizar una llamada a un origen de datos admitido.

Aquí es donde un solucionador local es útil. En lugar de llamar a un origen de datos remoto, el solucionador local simplemente **reenvía** los resultados del controlador de solicitudes al controlador de respuestas. La resolución de campo no sale de AWS AppSync.

Los solucionadores locales son útiles en una gran cantidad de situaciones. El caso de uso más habitual consiste en publicar notificaciones sin activar una llamada de origen de datos. Para demostrar este caso de uso, vamos a crear una aplicación pub/sub en la que los usuarios puedan publicar mensajes y suscribirse a ellos. Este ejemplo utiliza *suscripciones*, de modo que si no está familiarizado con las *suscripciones*, puede seguir el tutorial de [datos en tiempo real](aws-appsync-real-time-data.md).

## Creación de la aplicación pub/sub
<a name="create-the-pub-sub-application-js"></a>

Primero, cree una API de GraphQL vacía con la opción **Diseñar desde cero** y configure los detalles opcionales al crear la API de GraphQL.

En nuestra aplicación pub/sub, los clientes pueden suscribirse y publicar mensajes. Cada mensaje publicado incluye un nombre y datos. Añada lo siguiente al esquema:

```
type Channel {
	name: String!
	data: AWSJSON!
}

type Mutation {
	publish(name: String!, data: AWSJSON!): Channel
}

type Query {
	getChannel: Channel
}

type Subscription {
	subscribe(name: String!): Channel
		@aws_subscribe(mutations: ["publish"])
}
```

A continuación, asociaremos un solucionador al campo `Mutation.publish`. En el panel **Solucionadores** situado junto al panel **Esquema**, busque el tipo `Mutation`, después el campo `publish(...): Channel` y, a continuación, haga clic en **Asociar**.

Cree un origen de datos de tipo *Ninguno* y asígnele el nombre *PageDataSource*. Asócielo al solucionador.

Añada su implementación del solucionador mediante el siguiente fragmento de código:

```
export function request(ctx) {
  return { payload: ctx.args };
}

export function response(ctx) {
  return ctx.result;
}
```

Asegúrese de crear el solucionador y guardar los cambios realizados.

## Envíe y suscríbase a mensajes
<a name="send-and-subscribe-to-messages-js"></a>

Para que los clientes reciban mensajes, primero deben estar suscritos a una bandeja de entrada.

En el panel **Consultas**, ejecute la suscripción `SubscribeToData`:

```
subscription SubscribeToData {
    subscribe(name:"channel") {
        name
        data
    }
}
```

 El suscriptor recibirá mensajes siempre que se invoque la mutación `publish`, pero solo cuando el mensaje se envíe a la suscripción `channel`. Probemos esto en el panel **Consultas**. Mientras la suscripción siga ejecutándose en la consola, abra otra consola y ejecute la siguiente solicitud en el panel **Consultas**:

**nota**  
En este ejemplo, utilizamos cadenas JSON válidas.

```
mutation PublishData {
    publish(data: "{\"msg\": \"hello world!\"}", name: "channel") {
        data
        name
    }
}
```

El resultado tendrá este aspecto:

```
{
  "data": {
    "publish": {
      "data": "{\"msg\":\"hello world!\"}",
      "name": "channel"
    }
  }
}
```

Acabamos de demostrar el uso de solucionadores locales publicando un mensaje y recibiéndolo sin salir del servicio de AWS AppSync.

# Combinación de resolutores de GraphQL en AWS AppSync
<a name="tutorial-combining-graphql-resolvers-js"></a>

Los solucionadores y los campos de un esquema de GraphQL mantienen una relación 1:1 con un alto grado de flexibilidad. Debido a que un origen de datos se configura en un solucionador independientemente de un esquema, usted tiene la capacidad de resolver o manipular sus tipos de GraphQL a través de diferentes orígenes de datos, lo que le permite mezclar y combinar un esquema para satisfacer mejor sus necesidades.

Los siguientes escenarios muestran cómo mezclar y hacer corresponder los orígenes de datos en su esquema. Antes de empezar, debe estar familiarizado con la configuración de fuentes de datos y resolutores para AWS Lambda Amazon DynamoDB y Amazon Service. OpenSearch 

## Esquema de ejemplo
<a name="example-schema-js"></a>

El siguiente esquema tiene un tipo `Post` con tres `Query` y operaciones `Mutation` cada una:

```
type Post {
    id: ID!
    author: String!
    title: String
    content: String
    url: String
    ups: Int
    downs: Int
    version: Int!
}

type Query {
    allPost: [Post]
    getPost(id: ID!): Post
    searchPosts: [Post]
}

type Mutation {
    addPost(
        id: ID!,
        author: String!,
        title: String,
        content: String,
        url: String
    ): Post
    updatePost(
        id: ID!,
        author: String!,
        title: String,
        content: String,
        url: String,
        ups: Int!,
        downs: Int!,
        expectedVersion: Int!
    ): Post
    deletePost(id: ID!): Post
}
```

En este ejemplo, tendría un total de seis solucionadores y cada uno necesitaría un origen de datos. Una forma de resolver este problema sería conectarlos a una sola tabla de Amazon DynamoDB, `Posts` llamada, en la que `AllPost` el campo ejecute un escaneo y el campo ejecute una consulta ([JavaScriptconsulte `searchPosts` la referencia de funciones de resolución para](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html) DynamoDB). Sin embargo, no está limitado a Amazon DynamoDB; existen diferentes fuentes de datos, como Lambda OpenSearch o Service, para cumplir con los requisitos de su empresa. 

## Modificación de datos mediante solucionadores
<a name="alter-data-through-resolvers-js"></a>

Es posible que tenga que devolver los resultados de una base de datos de terceros que no sea compatible directamente con las fuentes de AWS AppSync datos. También es posible que tenga que realizar modificaciones complejas en los datos antes de devolverlos a los clientes de la API. Esto podría deberse a un formato incorrecto de los tipos de datos, como diferencias de fecha y hora en los clientes, o a la gestión de problemas de compatibilidad con versiones anteriores. En este caso, conectar AWS Lambda funciones como fuente de datos a tu AWS AppSync API es la solución adecuada. A modo ilustrativo, en el siguiente ejemplo, una AWS Lambda función manipula los datos obtenidos de un almacén de datos de terceros:

```
export const handler = (event, context, callback) => {
    // fetch data
    const result = fetcher()

    // apply complex business logic
    const data = transform(result)	

    // return to AppSync
    return data
};
```

Se trata de una función de Lambda perfectamente válida y puede asociarse al campo `AllPost` del esquema de GraphQL para que cualquier consulta que devuelva todos los resultados obtenga números aleatorios para los votos a favor y en contra.

## DynamoDB y Service OpenSearch
<a name="ddb-and-es-js"></a>

Para algunas aplicaciones, puede realizar mutaciones o consultas de búsqueda sencillas en DynamoDB y hacer que un proceso en segundo plano transfiera los documentos a Service. OpenSearch Simplemente puede adjuntar el `searchPosts` solucionador a la fuente de datos del OpenSearch servicio y devolver los resultados de la búsqueda (a partir de datos que se originaron en DynamoDB) mediante una consulta de GraphQL. Esto puede ser extremadamente útil al añadir operaciones de búsqueda avanzada a sus aplicaciones, como palabras clave, coincidencias parciales o incluso búsquedas geoespaciales. La transferencia de datos desde DynamoDB puede realizarse mediante un proceso ETL o, alternativamente, puede transmitir desde DynamoDB mediante Lambda.

Para empezar con estos orígenes de datos específicos, consulte nuestros tutoriales de [DynamoDB](https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-dynamodb-resolvers-js.html) y [Lambda](https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-lambda-resolvers-js.html).

Por ejemplo, con el esquema de nuestro tutorial anterior, la siguiente mutación añade un elemento a DynamoDB:

```
mutation addPost {
  addPost(
    id: 123
    author: "Nadia"
    title: "Our first post!"
    content: "This is our first post."
    url: "https://aws.amazon.com/appsync/"
  ) {
    id
    author
    title
    content
    url
    ups
    downs
    version
  }
}
```

Esto escribe los datos en DynamoDB, que luego los transmite a través de Lambda a OpenSearch Amazon Service, que luego se utilizan para buscar publicaciones por diferentes campos. Por ejemplo, dado que los datos están en Amazon OpenSearch Service, puedes buscar en el autor o en los campos de contenido con texto de formato libre, incluso con espacios, de la siguiente manera:

```
query searchName{
    searchAuthor(name:"   Nadia   "){
        id
        title
        content
    }
}

---------- or ----------

query searchContent{
    searchContent(text:"test"){
        id
        title
        content
    }
}
```

Como los datos se escriben directamente en DynamoDB, aún puede realizar operaciones eficientes de búsqueda de lista o de elemento en la tabla con las consultas `allPost{...}` y `getPost{...}`. Esta pila utiliza el siguiente código de ejemplo para los flujos de DynamoDB:

**nota**  
Este código de Python es un ejemplo y no está diseñado para usarse en código de producción.

```
import boto3
import requests
from requests_aws4auth import AWS4Auth

region = '' # e.g. us-east-1
service = 'es'
credentials = boto3.Session().get_credentials()
awsauth = AWS4Auth(credentials.access_key, credentials.secret_key, region, service, session_token=credentials.token)

host = '' # the OpenSearch Service domain, e.g. https://search-mydomain.us-west-1.es.amazonaws.com
index = 'lambda-index'
datatype = '_doc'
url = host + '/' + index + '/' + datatype + '/'

headers = { "Content-Type": "application/json" }

def handler(event, context):
    count = 0
    for record in event['Records']:
        # Get the primary key for use as the OpenSearch ID
        id = record['dynamodb']['Keys']['id']['S']

        if record['eventName'] == 'REMOVE':
            r = requests.delete(url + id, auth=awsauth)
        else:
            document = record['dynamodb']['NewImage']
            r = requests.put(url + id, auth=awsauth, json=document, headers=headers)
        count += 1
    return str(count) + ' records processed.'
```

A continuación, puede usar las transmisiones de DynamoDB para adjuntarlas a una tabla de DynamoDB con una clave principal de, y cualquier cambio en la fuente `id` de DynamoDB se transmitirá a su dominio de servicio. OpenSearch Para obtener más información sobre cómo configurarlo, consulte la [documentación de flujos de DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Streams.Lambda.html).

# Uso de resolutores OpenSearch de Amazon Service en AWS AppSync
<a name="tutorial-elasticsearch-resolvers-js"></a>

AWS AppSync admite el uso de Amazon OpenSearch Service desde dominios que hayas aprovisionado en tu propia AWS cuenta, siempre que no existan dentro de una VPC. Después de aprovisionar los dominios, puede conectarse a ellos con un origen de datos. En ese momento, puede configurar un solucionador en el esquema para que realice operaciones de GraphQL como consultas, mutaciones y suscripciones. Este tutorial le guiará a lo largo de algunos ejemplos comunes.

Para obtener más información, consulte nuestra [referencia de funciones JavaScript de resolución para](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-reference-elasticsearch-js.html). OpenSearch

## Cree un nuevo dominio OpenSearch de servicio
<a name="create-a-new-es-domain-js"></a>

Para empezar con este tutorial, necesitas un dominio de OpenSearch servicio existente. Si todavía no tiene uno, puede utilizar la siguiente muestra. Tenga en cuenta que la creación de un dominio de OpenSearch servicio puede tardar hasta 15 minutos antes de que pueda pasar a integrarlo con una fuente de AWS AppSync datos.

```
aws cloudformation create-stack --stack-name AppSyncOpenSearch \
--template-url https://s3.us-west-2.amazonaws.com/awsappsync/resources/elasticsearch/ESResolverCFTemplate.yaml \
--parameters ParameterKey=OSDomainName,ParameterValue=ddtestdomain ParameterKey=Tier,ParameterValue=development \
--capabilities CAPABILITY_NAMED_IAM
```

Puedes lanzar la siguiente AWS CloudFormation pila en la región US-West-2 (Oregón) de tu AWS cuenta:

 [https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/elasticsearch/ESResolverCFTemplate.yaml](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/elasticsearch/ESResolverCFTemplate.yaml)

## Configure una fuente de datos para el servicio OpenSearch
<a name="configure-data-source-for-es-js"></a>

Una vez creado el dominio de OpenSearch servicio, dirígete a tu API de AWS AppSync GraphQL y selecciona la pestaña **Fuentes de datos**. Selecciona **Crear fuente de datos** e introduce un nombre descriptivo para la fuente de datos, como «*oss*». A continuación, elige el ** OpenSearch dominio de Amazon** como **tipo de fuente de datos**, elige la región correspondiente y verás tu dominio de OpenSearch servicio en la lista. Después de seleccionarlo, puede crear un nuevo rol y AWS AppSync asignarle los permisos correspondientes, o puede elegir un rol existente, que tenga la siguiente política en línea:

También tendrás que establecer una relación de confianza AWS AppSync para ese rol:

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

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "appsync.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
```

------

Además, el dominio del OpenSearch servicio tiene su propia **política de acceso** que puedes modificar a través de la consola de Amazon OpenSearch Service. Debe añadir una política similar a la siguiente con las acciones y los recursos adecuados para el dominio del OpenSearch servicio. Tenga en cuenta que la función **principal** será la de fuente de AWS AppSync datos, que podrá encontrar en la consola de IAM si deja que dicha consola la cree.

## Conexión de un solucionador
<a name="connecting-a-resolver-js"></a>

Ahora que la fuente de datos está conectada a tu dominio de OpenSearch servicio, puedes conectarla a tu esquema de GraphQL con un solucionador, como se muestra en el siguiente ejemplo:

```
 type Query {
   getPost(id: ID!): Post
   allPosts: [Post]
 }

 type Mutation {
   addPost(id: ID!, author: String, title: String, url: String, ups: Int, downs: Int, content: String): AWSJSON
 }

type Post {
  id: ID!
  author: String
  title: String
  url: String
  ups: Int
  downs: Int
  content: String
}
```

Observe que hay un tipo `Post` definido por el usuario con un campo `id`. En los siguientes ejemplos, suponemos que hay un proceso (que se puede automatizar) para colocar este tipo en tu dominio de OpenSearch servicio, que se asignaría a la raíz de la ruta `/post/_doc` donde `post` se encuentra el índice. A partir de esta ruta raíz, puede realizar búsquedas de documentos individuales, búsquedas de comodín con `/id/post*` o búsquedas en varios documentos con la ruta `/post/_search`. Por ejemplo, si tiene otro tipo llamado `User`, puede indexar documentos bajo un nuevo índice llamado `user`, y luego realizar búsquedas con una **ruta** de `/user/_search`. 

En el editor de **esquemas** de la AWS AppSync consola, modifique el `Posts` esquema anterior para incluir una `searchPosts` consulta:

```
type Query {
  getPost(id: ID!): Post
  allPosts: [Post]
  searchPosts: [Post]
}
```

Guarde el esquema. En el panel **Solucionadores**, busque `searchPosts` y seleccione **Asociar**. Elija la fuente OpenSearch de datos del servicio y guarde la resolución. Actualice el código del solucionador mediante el siguiente fragmento:

```
import { util } from '@aws-appsync/utils'

/**
 * Searches for documents by using an input term
 * @param {import('@aws-appsync/utils').Context} ctx the context
 * @returns {*} the request
 */
export function request(ctx) {
	return {
		operation: 'GET',
		path: `/post/_search`,
		params: { body: { from: 0, size: 50 } },
	}
}

/**
 * Returns the fetched items
 * @param {import('@aws-appsync/utils').Context} ctx the context
 * @returns {*} the result
 */
export function response(ctx) {
	if (ctx.error) {
		util.error(ctx.error.message, ctx.error.type)
	}
	return ctx.result.hits.hits.map((hit) => hit._source)
}
```

Esto supone que el esquema anterior contiene documentos que se han indexado en OpenSearch Service en el `post` campo. Si estructura los datos de manera diferente, tendrá que realizar una actualización como corresponda.

## Modificación de las búsquedas
<a name="modifying-your-searches-js"></a>

El controlador de solicitudes del solucionador anterior realiza una consulta sencilla para todos los registros. Supongamos que desea buscar por un autor específico. Además, supongamos que desea que ese autor sea un argumento definido en la consulta de GraphQL. En el editor de **esquemas** de la AWS AppSync consola, añada una `allPostsByAuthor` consulta:

```
type Query {
  getPost(id: ID!): Post
  allPosts: [Post]
  allPostsByAuthor(author: String!): [Post]
  searchPosts: [Post]
}
```

En el panel **Solucionadores**, busque `allPostsByAuthor` y seleccione **Asociar**. Elija la fuente OpenSearch de datos del servicio y utilice el siguiente código:

```
import { util } from '@aws-appsync/utils'

/**
 * Searches for documents by `author`
 * @param {import('@aws-appsync/utils').Context} ctx the context
 * @returns {*} the request
 */
export function request(ctx) {
	return {
		operation: 'GET',
		path: '/post/_search',
		params: {
			body: {
				from: 0,
				size: 50,
				query: { match: { author: ctx.args.author } },
			},
		},
	}
}

/**
 * Returns the fetched items
 * @param {import('@aws-appsync/utils').Context} ctx the context
 * @returns {*} the result
 */
export function response(ctx) {
	if (ctx.error) {
		util.error(ctx.error.message, ctx.error.type)
	}
	return ctx.result.hits.hits.map((hit) => hit._source)
}
```

Observe que `body` se llena con una consulta de término para el campo `author`, que se pasa desde el cliente como un argumento. Si lo desea, puede usar información previamente rellenada, como texto estándar.

## Añadir datos al OpenSearch servicio
<a name="adding-data-to-es-js"></a>

Es posible que desee añadir datos a su dominio de OpenSearch servicio como resultado de una mutación de GraphQL. Se trata de un eficaz mecanismo para realizar búsquedas y para otros fines. Como puedes usar las suscripciones de GraphQL para [crear tus datos en tiempo real](aws-appsync-real-time-data.md), puede servir como un mecanismo para notificar a los clientes las actualizaciones de los datos en tu OpenSearch dominio de servicio.

Vuelve a la página del **esquema** de la AWS AppSync consola y selecciona **Adjuntar para ver** la `addPost()` mutación. Vuelva a seleccionar la fuente de datos del OpenSearch servicio y utilice el siguiente código:

```
import { util } from '@aws-appsync/utils'

/**
 * Searches for documents by `author`
 * @param {import('@aws-appsync/utils').Context} ctx the context
 * @returns {*} the request
 */
export function request(ctx) {
	return {
		operation: 'PUT',
		path: `/post/_doc/${ctx.args.id}`,
		params: { body: ctx.args },
	}
}

/**
 * Returns the inserted post
 * @param {import('@aws-appsync/utils').Context} ctx the context
 * @returns {*} the result
 */
export function response(ctx) {
	if (ctx.error) {
		util.error(ctx.error.message, ctx.error.type)
	}
	return ctx.result
}
```

Como antes, este es un ejemplo de cómo pueden estar estructurados los datos. Si los nombres de campos o índices son distintos, debe actualizar la `path` y `body`. En este ejemplo, también se muestra cómo utilizar `context.arguments`, que también se puede escribir como `ctx.args`, en el controlador de solicitudes.

## Recuperación de un solo documento
<a name="retrieving-a-single-document-js"></a>

Por último, si desea utilizar la `getPost(id:ID)` consulta en su esquema para obtener un documento individual, busque esta consulta en el editor de **esquemas** de la AWS AppSync consola y seleccione **Adjuntar**. Vuelva a seleccionar la fuente de datos del OpenSearch servicio y utilice el siguiente código:

```
import { util } from '@aws-appsync/utils'

/**
 * Searches for documents by `author`
 * @param {import('@aws-appsync/utils').Context} ctx the context
 * @returns {*} the request
 */
export function request(ctx) {
	return {
		operation: 'GET',
		path: `/post/_doc/${ctx.args.id}`,
	}
}

/**
 * Returns the post
 * @param {import('@aws-appsync/utils').Context} ctx the context
 * @returns {*} the result
 */
export function response(ctx) {
	if (ctx.error) {
		util.error(ctx.error.message, ctx.error.type)
	}
	return ctx.result._source
}
```

## Ejecución de consultas y mutaciones
<a name="tutorial-elasticsearch-resolvers-perform-queries-mutations-js"></a>

Ahora deberías poder realizar operaciones de GraphQL en tu dominio de OpenSearch servicio. Ve a la pestaña **Consultas** de la AWS AppSync consola y agrega un registro nuevo:

```
mutation AddPost {
    addPost (
        id:"12345"
        author: "Fred"
        title: "My first book"
        content: "This will be fun to write!"
        url: "publisher website",
        ups: 100,
        downs:20 
       )
}
```

Verá el resultado de la mutación a la derecha. Del mismo modo, ahora puedes ejecutar una `searchPosts` consulta en tu dominio OpenSearch de servicio:

```
query search {
    searchPosts {
        id
        title
        author
        content
    }
}
```

## Prácticas recomendadas
<a name="best-practices-js"></a>
+ OpenSearch El servicio debe ser para consultar datos, no como su base de datos principal. Es posible que desee utilizar el OpenSearch Servicio junto con Amazon DynamoDB, tal y como se describe en [Combinación](https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-combining-graphql-resolvers-js.html) de Resolvers de GraphQL.
+ Conceda acceso a su dominio únicamente si permite que el rol de AWS AppSync servicio acceda al clúster.
+ Puede comenzar con un pequeño desarrollo, con el clúster de menor costo y, a continuación, pasar a un clúster de mayor tamaño con alta disponibilidad (HA) al pasar a producción.

# Realizar transacciones de DynamoDB en AWS AppSync
<a name="tutorial-dynamodb-transact-js"></a>

AWS AppSync admite el uso de operaciones de transacciones de Amazon DynamoDB en una o más tablas de una sola región. Las operaciones admitidas son `TransactGetItems` y `TransactWriteItems`. Al utilizar estas funciones en AWS AppSync, puede realizar tareas como:
+ Transferir una lista de claves en una sola consulta y devolver los resultados desde una tabla
+ Leer registros desde una o varias tablas en una única consulta
+ Escribir los registros de las transacciones en una o más tablas de all-or-nothing alguna manera
+ Ejecutar transacciones cuando se cumplan algunas condiciones

## Permisos
<a name="permissions-js"></a>

Al igual que otros solucionadores, debe crear una fuente de datos AWS AppSync y crear un rol o usar uno existente. Dado que las operaciones de transacciones requieren diferentes permisos para las tablas de DynamoDB, debe conceder los permisos de rol configurados para las acciones de lectura o escritura:

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

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Action": [
                "dynamodb:DeleteItem",
                "dynamodb:GetItem",
                "dynamodb:PutItem",
                "dynamodb:Query",
                "dynamodb:Scan",
                "dynamodb:UpdateItem"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:dynamodb:us-east-1:111122223333:table/TABLENAME",
                "arn:aws:dynamodb:us-east-1:111122223333:table/TABLENAME/*"
            ]
        }
    ]
}
```

------

**nota**  
Los roles están vinculados a las fuentes de datos y los AWS AppSync resolutores de los campos se invocan en función de una fuente de datos. Los orígenes de datos configurados para recuperar información de DynamoDB solo tienen especificada una tabla para que la configuración siga siendo sencilla. Por lo tanto, al realizar una operación de transacciones en varias tablas con un único solucionador, que es una tarea más avanzada, debe conceder al rol de ese origen de datos acceso a cualquier tabla con la que el solucionador vaya a interactuar. Esto se hace en el campo **Resource (Recurso)** de la política de IAM anterior. La configuración de las tablas en las que se realizan llamadas de transacciones se lleva a cabo en el código del solucionador, que se describe a continuación.

## Origen de datos
<a name="data-source-js"></a>

En aras de la simplicidad, vamos a utilizar el mismo origen de datos para todos los solucionadores que se utilizan en este tutorial. 

Tendremos dos tablas denominadas **savingAccounts** y **checkingAccounts**, ambas con la clave de partición `accountNumber`, y una tabla **transactionHistory** con la clave de partición `transactionId`. Puede utilizar los siguientes comandos de la CLI para crear las tablas. Asegúrese de reemplazar `region` por la región.

**Con la CLI**

```
aws dynamodb create-table --table-name savingAccounts \
  --attribute-definitions AttributeName=accountNumber,AttributeType=S \
  --key-schema AttributeName=accountNumber,KeyType=HASH \
  --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 \
  --table-class STANDARD --region region

aws dynamodb create-table --table-name checkingAccounts \
  --attribute-definitions AttributeName=accountNumber,AttributeType=S \
  --key-schema AttributeName=accountNumber,KeyType=HASH \
  --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 \
  --table-class STANDARD --region region

aws dynamodb create-table --table-name transactionHistory \
  --attribute-definitions AttributeName=transactionId,AttributeType=S \
  --key-schema AttributeName=transactionId,KeyType=HASH \
  --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 \
  --table-class STANDARD --region region
```

En la AWS AppSync consola, en **Fuentes de datos**, cree una nueva fuente de datos de DynamoDB y asígnele un nombre. **TransactTutorial** Seleccione **savingAccounts** en la tabla (aunque la tabla específica no importa cuando se utilizan transacciones). Elija crear un nuevo rol y el origen de datos. Puede revisar la configuración del origen de datos para ver el nombre del rol generado. En la consola de IAM, puede añadir una política en línea que permita que el origen de datos interactúe con todas las tablas.

Sustituya `region` y `accountID` por su región y su identificador de cuenta:

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

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Action": [
                "dynamodb:DeleteItem",
                "dynamodb:GetItem",
                "dynamodb:PutItem",
                "dynamodb:Query",
                "dynamodb:Scan",
                "dynamodb:UpdateItem"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:dynamodb:us-east-1:111122223333:table/savingAccounts",
                "arn:aws:dynamodb:us-east-1:111122223333:table/savingAccounts/*",
                "arn:aws:dynamodb:us-east-1:111122223333:table/checkingAccounts",
                "arn:aws:dynamodb:us-east-1:111122223333:table/checkingAccounts/*",
                "arn:aws:dynamodb:us-east-1:111122223333:table/transactionHistory",
                "arn:aws:dynamodb:us-east-1:111122223333:table/transactionHistory/*"
            ]
        }
    ]
}
```

------

## Transacciones
<a name="transactions-js"></a>

Para este ejemplo, el contexto es una transacción bancaria clásica, en la que usaremos `TransactWriteItems` para:
+ Transferir dinero de cuentas de ahorro a cuentas corrientes
+ Generar nuevos registros de transacciones para cada transacción

Y, a continuación, usaremos `TransactGetItems` para recuperar los detalles de las cuentas de ahorro y las cuentas corrientes.

**aviso**  
`TransactWriteItems` no se admite cuando se utiliza con la detección y resolución de conflictos. Esta configuración debe estar deshabilitada para evitar posibles errores.

Definimos nuestro esquema GraphQL de la siguiente manera:

```
type SavingAccount {
    accountNumber: String!
    username: String
    balance: Float
}

type CheckingAccount {
    accountNumber: String!
    username: String
    balance: Float
}

type TransactionHistory {
    transactionId: ID!
    from: String
    to: String
    amount: Float
}

type TransactionResult {
    savingAccounts: [SavingAccount]
    checkingAccounts: [CheckingAccount]
    transactionHistory: [TransactionHistory]
}

input SavingAccountInput {
    accountNumber: String!
    username: String
    balance: Float
}

input CheckingAccountInput {
    accountNumber: String!
    username: String
    balance: Float
}

input TransactionInput {
    savingAccountNumber: String!
    checkingAccountNumber: String!
    amount: Float!
}

type Query {
    getAccounts(savingAccountNumbers: [String], checkingAccountNumbers: [String]): TransactionResult
}

type Mutation {
    populateAccounts(savingAccounts: [SavingAccountInput], checkingAccounts: [CheckingAccountInput]): TransactionResult
    transferMoney(transactions: [TransactionInput]): TransactionResult
}
```

### TransactWriteItems - Rellene las cuentas
<a name="transactwriteitems-populate-accounts-js"></a>

Para transferir dinero entre cuentas, tenemos que rellenar la tabla con los detalles. Para ello, utilizaremos la operación `Mutation.populateAccounts` de GraphQL.

En la sección Esquema, haga clic en **Asociar** junto a la operación `Mutation.populateAccounts`. Elija el origen de datos `TransactTutorial` y, a continuación, seleccione **Crear**.

Ahora utilice el siguiente código:

```
import { util } from '@aws-appsync/utils'

export function request(ctx) {
	const { savingAccounts, checkingAccounts } = ctx.args

	const savings = savingAccounts.map(({ accountNumber, ...rest }) => {
		return {
			table: 'savingAccounts',
			operation: 'PutItem',
			key: util.dynamodb.toMapValues({ accountNumber }),
			attributeValues: util.dynamodb.toMapValues(rest),
		}
	})

	const checkings = checkingAccounts.map(({ accountNumber, ...rest }) => {
		return {
			table: 'checkingAccounts',
			operation: 'PutItem',
			key: util.dynamodb.toMapValues({ accountNumber }),
			attributeValues: util.dynamodb.toMapValues(rest),
		}
	})
	return {
		version: '2018-05-29',
		operation: 'TransactWriteItems',
		transactItems: [...savings, ...checkings],
	}
}

export function response(ctx) {
	if (ctx.error) {
		util.error(ctx.error.message, ctx.error.type, null, ctx.result.cancellationReasons)
	}
	const { savingAccounts: sInput, checkingAccounts: cInput } = ctx.args
	const keys = ctx.result.keys
	const savingAccounts = sInput.map((_, i) => keys[i])
	const sLength = sInput.length
	const checkingAccounts = cInput.map((_, i) => keys[sLength + i])
	return { savingAccounts, checkingAccounts }
}
```

Guarde la resolución y vaya a la sección **Consultas** de la AWS AppSync consola para rellenar las cuentas.

Ejecute la mutación siguiente:

```
mutation populateAccounts {
  populateAccounts (
    savingAccounts: [
      {accountNumber: "1", username: "Tom", balance: 100},
      {accountNumber: "2", username: "Amy", balance: 90},
      {accountNumber: "3", username: "Lily", balance: 80},
    ]
    checkingAccounts: [
      {accountNumber: "1", username: "Tom", balance: 70},
      {accountNumber: "2", username: "Amy", balance: 60},
      {accountNumber: "3", username: "Lily", balance: 50},
    ]) {
    savingAccounts {
      accountNumber
    }
    checkingAccounts {
      accountNumber
    }
  }
}
```

Hemos rellenado tres cuentas de ahorro y tres cuentas corrientes en una mutación.

Utilice la consola de DynamoDB para validar que los datos se muestren en las tablas **savingAccounts** y **checkingAccounts**.

### TransactWriteItems - Transfiere dinero
<a name="transactwriteitems-transfer-money-js"></a>

Asocie un solucionador a la mutación `transferMoney` con el siguiente código. Para cada transferencia, necesitamos un modificador de éxito tanto en la cuenta corriente como en la de ahorros, y debemos hacer un seguimiento de la transferencia en las transacciones.

```
import { util } from '@aws-appsync/utils'

export function request(ctx) {
	const transactions = ctx.args.transactions

	const savings = []
	const checkings = []
	const history = []
	transactions.forEach((t) => {
		const { savingAccountNumber, checkingAccountNumber, amount } = t
		savings.push({
			table: 'savingAccounts',
			operation: 'UpdateItem',
			key: util.dynamodb.toMapValues({ accountNumber: savingAccountNumber }),
			update: {
				expression: 'SET balance = balance - :amount',
				expressionValues: util.dynamodb.toMapValues({ ':amount': amount }),
			},
		})
		checkings.push({
			table: 'checkingAccounts',
			operation: 'UpdateItem',
			key: util.dynamodb.toMapValues({ accountNumber: checkingAccountNumber }),
			update: {
				expression: 'SET balance = balance + :amount',
				expressionValues: util.dynamodb.toMapValues({ ':amount': amount }),
			},
		})
		history.push({
			table: 'transactionHistory',
			operation: 'PutItem',
			key: util.dynamodb.toMapValues({ transactionId: util.autoId() }),
			attributeValues: util.dynamodb.toMapValues({
				from: savingAccountNumber,
				to: checkingAccountNumber,
				amount,
			}),
		})
	})

	return {
		version: '2018-05-29',
		operation: 'TransactWriteItems',
		transactItems: [...savings, ...checkings, ...history],
	}
}

export function response(ctx) {
	if (ctx.error) {
		util.error(ctx.error.message, ctx.error.type, null, ctx.result.cancellationReasons)
	}
	const tInput = ctx.args.transactions
	const tLength = tInput.length
	const keys = ctx.result.keys
	const savingAccounts = tInput.map((_, i) => keys[tLength * 0 + i])
	const checkingAccounts = tInput.map((_, i) => keys[tLength * 1 + i])
	const transactionHistory = tInput.map((_, i) => keys[tLength * 2 + i])
	return { savingAccounts, checkingAccounts, transactionHistory }
}
```

Ahora, navega a la sección **Consultas** de la AWS AppSync consola y ejecuta la mutación **TransferMoney** de la siguiente manera:

```
mutation write {
  transferMoney(
    transactions: [
      {savingAccountNumber: "1", checkingAccountNumber: "1", amount: 7.5},
      {savingAccountNumber: "2", checkingAccountNumber: "2", amount: 6.0},
      {savingAccountNumber: "3", checkingAccountNumber: "3", amount: 3.3}
    ]) {
    savingAccounts {
      accountNumber
    }
    checkingAccounts {
      accountNumber
    }
    transactionHistory {
      transactionId
    }
  }
}
```

Hemos enviado tres transacciones bancarias en una mutación. Utilice la consola de DynamoDB para validar que los datos se muestren en las tablas **savingAccounts**, **checkingAccounts** y **transactionHistory**.

### TransactGetItems - Recupera cuentas
<a name="transactgetitems-retrieve-accounts-js"></a>

Con el fin de recuperar los detalles de las cuentas de ahorro y corriente en una sola solicitud transaccional, vamos a asociar un solucionador a la operación `Query.getAccounts` de GraphQL en nuestro esquema. Seleccione **Asociar** y elija el origen de datos `TransactTutorial` creado al inicio del tutorial. Utilice el siguiente código: 

```
import { util } from '@aws-appsync/utils'

export function request(ctx) {
	const { savingAccountNumbers, checkingAccountNumbers } = ctx.args

	const savings = savingAccountNumbers.map((accountNumber) => {
		return { table: 'savingAccounts', key: util.dynamodb.toMapValues({ accountNumber }) }
	})
	const checkings = checkingAccountNumbers.map((accountNumber) => {
		return { table: 'checkingAccounts', key: util.dynamodb.toMapValues({ accountNumber }) }
	})
	return {
		version: '2018-05-29',
		operation: 'TransactGetItems',
		transactItems: [...savings, ...checkings],
	}
}

export function response(ctx) {
	if (ctx.error) {
		util.error(ctx.error.message, ctx.error.type, null, ctx.result.cancellationReasons)
	}

	const { savingAccountNumbers: sInput, checkingAccountNumbers: cInput } = ctx.args
	const items = ctx.result.items
	const savingAccounts = sInput.map((_, i) => items[i])
	const sLength = sInput.length
	const checkingAccounts = cInput.map((_, i) => items[sLength + i])
	return { savingAccounts, checkingAccounts }
}
```

Guarde el solucionador y vaya a las secciones **Queries (Consultas)** de la consola de AWS AppSync . Para recuperar las cuentas de ahorro y corriente, ejecute la siguiente consulta:

```
query getAccounts {
  getAccounts(
    savingAccountNumbers: ["1", "2", "3"],
    checkingAccountNumbers: ["1", "2"]
  ) {
    savingAccounts {
      accountNumber
      username
      balance
    }
    checkingAccounts {
      accountNumber
      username
      balance
    }
  }
}
```

Hemos demostrado con éxito el uso de transacciones de DynamoDB mediante. AWS AppSync

# Uso de operaciones por lotes de DynamoDB en AWS AppSync
<a name="tutorial-dynamodb-batch-js"></a>

AWS AppSync admite el uso de operaciones por lotes de Amazon DynamoDB en una o más tablas de una sola región. Las operaciones admitidas son `BatchGetItem`, `BatchPutItem` y `BatchDeleteItem`. Al utilizar estas funciones en AWS AppSync, puede realizar tareas como:
+ Transferir una lista de claves en una sola consulta y devolver los resultados desde una tabla
+ Leer registros desde una o varias tablas en una única consulta
+ Escribir registros de forma masiva en una o varias tablas
+ Escribir o eliminar condicionalmente registros en varias tablas que pueden tener una relación

Las operaciones por lotes AWS AppSync tienen dos diferencias clave con respecto a las operaciones sin lotes:
+ El rol del origen de datos debe tener permisos para todas las tablas a las que el solucionador obtiene acceso.
+ La especificación de tabla de un solucionador forma parte del objeto de solicitud.

## Lotes en una única tabla
<a name="single-table-batch-js"></a>

**aviso**  
`BatchPutItem` y `BatchDeleteItem` no se admiten cuando se utilizan con la detección y resolución de conflictos. Esta configuración debe estar deshabilitada para evitar posibles errores.

Para empezar, vamos a crear una nueva API de GraphQL. En la AWS AppSync consola, selecciona **Crear API**, **GraphQL** y APIs **Diseñar desde cero**. Asigne a su API el nombre `BatchTutorial API`, elija **Siguiente** y, en el paso **Especificar recursos de GraphQL**, elija **Crear recursos de GraphQL más adelante**. Luego, haga clic en **Siguiente**. Revise sus detalles y cree la API. Ve a la página de **esquemas** y pega el siguiente esquema. Ten en cuenta que, para la consulta, pasaremos una lista de IDs:

```
type Post {
    id: ID!
    title: String
}

input PostInput {
    id: ID!
    title: String
}

type Query {
    batchGet(ids: [ID]): [Post]
}

type Mutation {
    batchAdd(posts: [PostInput]): [Post]
    batchDelete(ids: [ID]): [Post]
}
```

Guarde el esquema y elija **Crear recursos** en la parte superior de la página. Elija **Usar tipo existente** y, a continuación, elija el tipo `Post`. Llame a su tabla `Posts`. Asegúrese de que **Clave principal** está establecido en `id`, desmarque **Generar GraphQL automáticamente** (usted proporcionará su propio código) y seleccione **Crear**. Para empezar, AWS AppSync crea una nueva tabla de DynamoDB y un origen de datos conectado a la tabla con los roles adecuados. Sin embargo, todavía hay un par de permisos que debe añadir al rol. Vaya a la página **Orígenes de datos** y elija el nuevo origen de datos. En **Seleccionar un rol existente**, verá que se ha creado automáticamente un rol para la tabla. Tome nota del rol (debería tener un aspecto similar`appsync-ds-ddb-aaabbbcccddd-Posts`) y, a continuación, vaya a la consola de IAM ([https://console.aws.amazon.com/iam/](https://console.aws.amazon.com/iam/)). En la consola de IAM, seleccione **Roles** y, a continuación, seleccione su rol en la tabla. En su rol, en **Políticas de permisos**, haga clic en el botón `+` situado junto a la política (debe tener un nombre similar al del rol). Seleccione **Editar** en la parte superior del menú desplegable cuando aparezca la política. Debe añadir permisos por lotes a su política, específicamente `dynamodb:BatchGetItem` y `dynamodb:BatchWriteItem`. Tendrá un aspecto similar al siguiente:

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

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:DeleteItem",
                "dynamodb:GetItem",
                "dynamodb:PutItem",
                "dynamodb:Query",
                "dynamodb:Scan",
                "dynamodb:UpdateItem",
                "dynamodb:BatchWriteItem",
                "dynamodb:BatchGetItem"
            ],
            "Resource": [
                "arn:aws:dynamodb:us-east-1:111122223333:table/locationReadings",
                "arn:aws:dynamodb:us-east-1:111122223333:table/locationReadings/*",
                "arn:aws:dynamodb:us-east-1:111122223333:table/temperatureReadings",
                "arn:aws:dynamodb:us-east-1:111122223333:table/temperatureReadings/*"
            ]
        }
    ]
}
```

------

Seleccione **Siguiente** y, a continuación, **Guardar cambios**. Su política debería permitir ahora el procesamiento por lotes.

De vuelta a la AWS AppSync consola, ve a la página del **esquema** y selecciona **Adjuntar** junto al `Mutation.batchAdd` campo. Cree su solucionador utilizando la tabla `Posts` como origen de datos. En el editor de código, sustituya los controladores por el siguiente fragmento. Este fragmento toma automáticamente cada elemento con el tipo de `input PostInput` de GraphQL y crea un mapa, lo que es necesario para la operación `BatchPutItem`:

```
import { util } from "@aws-appsync/utils";

export function request(ctx) {
  return {
    operation: "BatchPutItem",
    tables: {
      Posts: ctx.args.posts.map((post) => util.dynamodb.toMapValues(post)),
    },
  };
}

export function response(ctx) {
  if (ctx.error) {
    util.error(ctx.error.message, ctx.error.type);
  }
  return ctx.result.data.Posts;
}
```

Vaya a la página de **consultas** de la AWS AppSync consola y ejecute la siguiente `batchAdd` mutación:

```
mutation add {
    batchAdd(posts:[{
            id: 1 title: "Running in the Park"},{
            id: 2 title: "Playing fetch"
        }]){
            id
            title
    }
}
```

Debería ver los resultados impresos en la pantalla; para validarlo, consulte la consola de DynamoDB para buscar los valores escritos en la tabla `Posts`.

A continuación, repita el proceso para asociar un solucionador, pero para el campo `Query.batchGet`, utilice la tabla `Posts` como origen de datos. Sustituya los controladores por el siguiente código. Esto toma automáticamente cada elemento del tipo de GraphQL `ids:[]` y crea un mapa que es necesario para la operación `BatchGetItem`:

```
import { util } from "@aws-appsync/utils";

export function request(ctx) {
  return {
    operation: "BatchGetItem",
    tables: {
      Posts: {
        keys: ctx.args.ids.map((id) => util.dynamodb.toMapValues({ id })),
        consistentRead: true,
      },
    },
  };
}

export function response(ctx) {
  if (ctx.error) {
    util.error(ctx.error.message, ctx.error.type);
  }
  return ctx.result.data.Posts;
}
```

Ahora, regrese a la página de **consultas** de la AWS AppSync consola y ejecute la siguiente `batchGet` consulta:

```
query get {
    batchGet(ids:[1,2,3]){
        id
        title
    }
}
```

Esto debe devolver los resultados para los dos valores `id` que ha añadido anteriormente. Observe que se devuelve un valor `null` para el `id` con el valor `3`. Esto se debe a que aún no hay ningún registro en la tabla `Posts` con ese valor. Tenga en cuenta también que AWS AppSync devuelve los resultados en el mismo orden que las claves transferidas a la consulta, que es una función adicional que AWS AppSync funciona en su nombre. Por lo tanto, si cambia a `batchGet(ids:[1,3,2])`, verá que el orden cambia. También sabrá por qué `id` devuelve un valor `null`.

Por último, asocie un solucionador más al campo `Mutation.batchDelete` utilizando la tabla `Posts` como origen de datos. Sustituya los controladores por el siguiente código. Esto toma automáticamente cada elemento del tipo de GraphQL `ids:[]` y crea un mapa que es necesario para la operación `BatchGetItem`:

```
import { util } from "@aws-appsync/utils";

export function request(ctx) {
  return {
    operation: "BatchDeleteItem",
    tables: {
      Posts: ctx.args.ids.map((id) => util.dynamodb.toMapValues({ id })),
    },
  };
}

export function response(ctx) {
  if (ctx.error) {
    util.error(ctx.error.message, ctx.error.type);
  }
  return ctx.result.data.Posts;
}
```

Ahora, vuelve a la página de **consultas** de la AWS AppSync consola y ejecuta la siguiente `batchDelete` mutación:

```
mutation delete {
    batchDelete(ids:[1,2]){ id }
}
```

Ahora se eliminarán los registros con `id` `1` y `2`. Si vuelve a ejecutar la consulta `batchGet()` anterior, devolverá `null`.

## Lotes en varias tablas
<a name="multi-table-batch-js"></a>

**aviso**  
`BatchPutItem` y `BatchDeleteItem` no se admiten cuando se utilizan con la detección y resolución de conflictos. Esta configuración debe estar deshabilitada para evitar posibles errores.

AWS AppSync también permite realizar operaciones por lotes en todas las tablas. Vamos a crear una aplicación más compleja. Imagine que queremos crear una aplicación de salud para mascotas, con sensores que comunican la ubicación y temperatura corporal de la mascota. Los sensores funcionan con pilas e intentan conectarse a la red cada pocos minutos. Cuando un sensor establece una conexión, envía sus lecturas a nuestra AWS AppSync API. A continuación, los disparadores analizan los datos para presentar un panel al propietario de la mascota. Concentrémonos en representar las interacciones entre el sensor y el almacén de datos de backend.

En la AWS AppSync consola, selecciona **Crear API**, **GraphQL** y APIs **Diseñar desde cero**. Asigne a su API el nombre `MultiBatchTutorial API`, elija **Siguiente** y, en el paso **Especificar recursos de GraphQL**, elija **Crear recursos de GraphQL más adelante**. Luego, haga clic en **Siguiente**. Revise sus detalles y cree la API. Vaya a la página **Esquema** y pegue y guarde el siguiente esquema:

```
type Mutation {
    # Register a batch of readings
    recordReadings(tempReadings: [TemperatureReadingInput], locReadings: [LocationReadingInput]): RecordResult
    # Delete a batch of readings
    deleteReadings(tempReadings: [TemperatureReadingInput], locReadings: [LocationReadingInput]): RecordResult
}

type Query {
    # Retrieve all possible readings recorded by a sensor at a specific time
    getReadings(sensorId: ID!, timestamp: String!): [SensorReading]
}

type RecordResult {
    temperatureReadings: [TemperatureReading]
    locationReadings: [LocationReading]
}

interface SensorReading {
    sensorId: ID!
    timestamp: String!
}

# Sensor reading representing the sensor temperature (in Fahrenheit)
type TemperatureReading implements SensorReading {
    sensorId: ID!
    timestamp: String!
    value: Float
}

# Sensor reading representing the sensor location (lat,long)
type LocationReading implements SensorReading {
    sensorId: ID!
    timestamp: String!
    lat: Float
    long: Float
}

input TemperatureReadingInput {
    sensorId: ID!
    timestamp: String
    value: Float
}

input LocationReadingInput {
    sensorId: ID!
    timestamp: String
    lat: Float
    long: Float
}
```

Necesitamos crear dos tablas de DynamoDB:
+ `locationReadings` almacenará las lecturas de ubicación de los sensores.
+ `temperatureReadings` almacenará las lecturas de temperatura de los sensores.

Ambas tablas compartirán la misma estructura de clave principal: `sensorId (String)` como clave de partición y `timestamp (String)` como clave de clasificación.

Elija **Crear recursos** en la parte superior de la página. Elija **Usar tipo existente** y, a continuación, elija el tipo `locationReadings`. Llame a su tabla `locationReadings`. Asegúrese de que **Clave principal** está configurada en `sensorId` y la clave de clasificación en`timestamp`. Desmarque **Generar GraphQL automáticamente** (proporcionará su propio código) y seleccione **Crear**. Repita este proceso para `temperatureReadings` utilizando `temperatureReadings` como tipo y nombre de la tabla. Utilice las mismas claves que antes.

Las nuevas tablas contendrán los roles generados automáticamente. Todavía hay que añadir un par de permisos a esos roles. Vaya a la página **Orígenes de datos** y elija `locationReadings`. En **Seleccione un rol existente**, puede ver el rol. Tome nota del rol (debería tener un aspecto similar`appsync-ds-ddb-aaabbbcccddd-locationReadings`) y, a continuación, vaya a la consola de IAM () [https://console.aws.amazon.com/iam/](https://console.aws.amazon.com/iam/). En la consola de IAM, seleccione **Roles** y, a continuación, seleccione su rol en la tabla. En su rol, en **Políticas de permisos**, haga clic en el botón `+` situado junto a la política (debe tener un nombre similar al del rol). Seleccione **Editar** en la parte superior del menú desplegable cuando aparezca la política. Debe añadir permisos a esta política. Tendrá un aspecto similar al siguiente:

Seleccione **Siguiente** y, a continuación, **Guardar cambios**. Repita este proceso para la el origen de datos `temperatureReadings` utilizando el mismo fragmento de política anterior.

### BatchPutItem - Grabar las lecturas de los sensores
<a name="batchputitem-recording-sensor-readings-js"></a>

Nuestros sensores tiene que poder enviar sus lecturas cuando se conectan a Internet. El campo de GraphQL `Mutation.recordReadings` es la API que utilizarán para hacerlo. Necesitamos añadir un solucionador a este campo.

En la página de **esquemas** de la AWS AppSync consola, selecciona **Adjuntar** junto al `Mutation.recordReadings` campo. En la siguiente pantalla, cree su solucionador utilizando la tabla `locationReadings` como origen de datos.

Tras crear el solucionador, sustituya los controladores por el siguiente código en el editor. Esta operación `BatchPutItem` nos permite especificar varias tablas: 

```
import { util } from '@aws-appsync/utils'

export function request(ctx) {
	const { locReadings, tempReadings } = ctx.args
	const locationReadings = locReadings.map((loc) => util.dynamodb.toMapValues(loc))
	const temperatureReadings = tempReadings.map((tmp) => util.dynamodb.toMapValues(tmp))

	return {
		operation: 'BatchPutItem',
		tables: {
			locationReadings,
			temperatureReadings,
		},
	}
}

export function response(ctx) {
	if (ctx.error) {
		util.appendError(ctx.error.message, ctx.error.type)
	}
	return ctx.result.data
}
```

Con operaciones por lotes, la invocación puede devolver tanto errores como resultados. Por lo tanto, podemos realizar algún control de errores adicional.

**nota**  
El uso de `utils.appendError()` es similar al de `util.error()`, con la gran diferencia de que no interrumpe la evaluación del controlador de solicitudes o respuestas. En su lugar, indica que hubo un error con el campo, pero permite al controlador evaluar la plantilla y, por tanto, devolver los datos al intermediario. Recomendamos que utilice `utils.appendError()` cuando la aplicación tenga que devolver resultados parciales.

Guarde el solucionador y vaya a la página de **consultas** de la AWS AppSync consola. Ahora podemos enviar algunas lecturas de los sensores.

Ejecute la mutación siguiente:

```
mutation sendReadings {
  recordReadings(
    tempReadings: [
      {sensorId: 1, value: 85.5, timestamp: "2018-02-01T17:21:05.000+08:00"},
      {sensorId: 1, value: 85.7, timestamp: "2018-02-01T17:21:06.000+08:00"},
      {sensorId: 1, value: 85.8, timestamp: "2018-02-01T17:21:07.000+08:00"},
      {sensorId: 1, value: 84.2, timestamp: "2018-02-01T17:21:08.000+08:00"},
      {sensorId: 1, value: 81.5, timestamp: "2018-02-01T17:21:09.000+08:00"}
    ]
    locReadings: [
      {sensorId: 1, lat: 47.615063, long: -122.333551, timestamp: "2018-02-01T17:21:05.000+08:00"},
      {sensorId: 1, lat: 47.615163, long: -122.333552, timestamp: "2018-02-01T17:21:06.000+08:00"},
      {sensorId: 1, lat: 47.615263, long: -122.333553, timestamp: "2018-02-01T17:21:07.000+08:00"},
      {sensorId: 1, lat: 47.615363, long: -122.333554, timestamp: "2018-02-01T17:21:08.000+08:00"},
      {sensorId: 1, lat: 47.615463, long: -122.333555, timestamp: "2018-02-01T17:21:09.000+08:00"}
    ]) {
    locationReadings {
      sensorId
      timestamp
      lat
      long
    }
    temperatureReadings {
      sensorId
      timestamp
      value
    }
  }
}
```

Enviamos diez lecturas de sensores en una mutación con lecturas repartidas en dos tablas. Utilice la consola de DynamoDB para validar que los datos aparecen en las tablas `locationReadings` y `temperatureReadings`.

### BatchDeleteItem - Eliminar las lecturas de los sensores
<a name="batchdeleteitem-deleting-sensor-readings-js"></a>

Del mismo modo, también es necesario poder eliminar lotes de lecturas de sensores. Vamos a utilizar el campo de GraphQL `Mutation.deleteReadings` para este fin. En la página de **esquema** de la AWS AppSync consola, selecciona **Adjuntar** junto al `Mutation.deleteReadings` campo. En la siguiente pantalla, cree su solucionador utilizando la tabla `locationReadings` como origen de datos.

Tras crear el solucionador, sustituya los controladores del editor de código por el siguiente fragmento. En este solucionador, utilizamos un mapeador de funciones auxiliar que extrae `sensorId` y `timestamp` de las entradas proporcionadas. 

```
import { util } from '@aws-appsync/utils'

export function request(ctx) {
	const { locReadings, tempReadings } = ctx.args
	const mapper = ({ sensorId, timestamp }) => util.dynamodb.toMapValues({ sensorId, timestamp })

	return {
		operation: 'BatchDeleteItem',
		tables: {
			locationReadings: locReadings.map(mapper),
			temperatureReadings: tempReadings.map(mapper),
		},
	}
}

export function response(ctx) {
	if (ctx.error) {
		util.appendError(ctx.error.message, ctx.error.type)
	}
	return ctx.result.data
}
```

Guarde el solucionador y vaya a la página de **consultas** de la AWS AppSync consola. Ahora vamos a eliminar un par de lecturas de sensores.

Ejecute la mutación siguiente:

```
mutation deleteReadings {
  # Let's delete the first two readings we recorded
  deleteReadings(
    tempReadings: [{sensorId: 1, timestamp: "2018-02-01T17:21:05.000+08:00"}]
    locReadings: [{sensorId: 1, timestamp: "2018-02-01T17:21:05.000+08:00"}]) {
    locationReadings {
      sensorId
      timestamp
      lat
      long
    }
    temperatureReadings {
      sensorId
      timestamp
      value
    }
  }
}
```

**nota**  
Al contrario que en la operación `DeleteItem`, el elemento completamente eliminado no se devuelve en la respuesta. Solo se devuelve la clave pasada. Para obtener más información, consulte la [referencia de BatchDeleteItem la JavaScript función de resolución para DynamoDB](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-batch-delete-item).

Valide a través de la consola de DynamoDB que estas dos lecturas se han eliminado de las tablas `locationReadings` y `temperatureReadings`.

### BatchGetItem - Recupera las lecturas
<a name="batchgetitem-retrieve-readings-js"></a>

Otra operación habitual en nuestra aplicación es obtener las lecturas de un sensor en un momento determinado. Vamos a asociar un solucionador al campo de GraphQL `Query.getReadings` en nuestro esquema. En la página de **esquema** de la AWS AppSync consola, selecciona **Adjuntar** junto al `Query.getReadings` campo. En la siguiente pantalla, cree su solucionador utilizando la tabla `locationReadings` como origen de datos.

Vamos a utilizar el siguiente código: 

```
import { util } from '@aws-appsync/utils'

export function request(ctx) {
	const keys = [util.dynamodb.toMapValues(ctx.args)]
	const consistentRead = true
	return {
		operation: 'BatchGetItem',
		tables: {
			locationReadings: { keys, consistentRead },
			temperatureReadings: { keys, consistentRead },
		},
	}
}

export function response(ctx) {
	if (ctx.error) {
		util.appendError(ctx.error.message, ctx.error.type)
	}
	const { locationReadings: locs, temperatureReadings: temps } = ctx.result.data

	return [
		...locs.map((l) => ({ ...l, __typename: 'LocationReading' })),
		...temps.map((t) => ({ ...t, __typename: 'TemperatureReading' })),
	]
}
```

Guarde el solucionador y vaya a la página de **consultas** de la AWS AppSync consola. Ahora vamos a recuperar las lecturas de nuestro sensor.

Ejecute la siguiente consulta:

```
query getReadingsForSensorAndTime {
  # Let's retrieve the very first two readings
  getReadings(sensorId: 1, timestamp: "2018-02-01T17:21:06.000+08:00") {
    sensorId
    timestamp
    ...on TemperatureReading {
      value
    }
    ...on LocationReading {
      lat
      long
    }
  }
}
```

Hemos demostrado con éxito el uso de las operaciones por lotes de DynamoDB mediante. AWS AppSync

## Gestión de errores
<a name="error-handling-js"></a>

En AWS AppSync, las operaciones de fuentes de datos a veces pueden arrojar resultados parciales. "Resultados parciales" es el término que utilizaremos para indicar que el resultado de una operación se compone de algunos datos y un error. Como la gestión de errores es intrínsecamente específica de la aplicación, AWS AppSync le brinda la oportunidad de gestionar los errores en el controlador de respuestas. El error de invocación del solucionador, si lo hay, está disponible en el contexto como `ctx.error`. Los errores de invocación siempre incluyen un mensaje y un tipo a los que se tiene acceso como propiedades `ctx.error.message` y `ctx.error.type`. En el controlador de respuestas, puede gestionar los resultados parciales de tres maneras:

1. Pasar por alto el error de la invocación y limitarse a devolver los datos.

1. Generar un error (con `util.error(...)`) y detener la evaluación del controlador, con lo que no se devuelven datos.

1. Agregar un error (con `util.appendError(...)`) y también devolver los datos.

Vamos a demostrar cada una de estas posibilidades con operaciones por lotes de DynamoDB.

### Operaciones por lotes de DynamoDB
<a name="dynamodb-batch-operations-js"></a>

Con las operaciones por lotes de DynamoDB, es posible que un lote se complete parcialmente. Es decir, es posible que algunos de los elementos o claves solicitados se queden sin procesar. Si AWS AppSync no puede completar un lote, los elementos no procesados y un error de invocación se establecerán en el contexto.

Vamos a implementar la gestión de errores utilizando la configuración del campo `Query.getReadings` de la operación `BatchGetItem` de la sección anterior de este tutorial. Esta vez, supongamos que mientras se ejecutaba el campo `Query.getReadings`, la tabla de DynamoDB `temperatureReadings` agotó el desempeño aprovisionado. DynamoDB generó `ProvisionedThroughputExceededException` un durante el segundo intento al procesar AWS AppSync los elementos restantes del lote.

El siguiente código JSON representa el contexto serializado después de la invocación por lotes de DynamoDB, pero antes de que se llamara al controlador de respuestas:

```
{
  "arguments": {
    "sensorId": "1",
    "timestamp": "2018-02-01T17:21:05.000+08:00"
  },
  "source": null,
  "result": {
    "data": {
      "temperatureReadings": [
        null
      ],
      "locationReadings": [
        {
          "lat": 47.615063,
          "long": -122.333551,
          "sensorId": "1",
          "timestamp": "2018-02-01T17:21:05.000+08:00"
        }
      ]
    },
    "unprocessedKeys": {
      "temperatureReadings": [
        {
          "sensorId": "1",
          "timestamp": "2018-02-01T17:21:05.000+08:00"
        }
      ],
      "locationReadings": []
    }
  },
  "error": {
    "type": "DynamoDB:ProvisionedThroughputExceededException",
    "message": "You exceeded your maximum allowed provisioned throughput for a table or for one or more global secondary indexes. (...)"
  },
  "outErrors": []
}
```

Cabe tener en cuenta algunos aspectos del contexto:
+ El error de invocación se estableció en el contexto en `ctx.error` by AWS AppSync y el tipo de error se estableció en. `DynamoDB:ProvisionedThroughputExceededException`
+ Los resultados se mapean para cada tabla en `ctx.result.data`, aunque haya un error.
+ Las claves que quedaron sin procesar están disponibles en `ctx.result.data.unprocessedKeys`. En este caso, no AWS AppSync se pudo recuperar el elemento con la clave (SensorID:1, TimeStamp:2018-02-01T 17:21:05.000 \$1 08:00) debido a un rendimiento insuficiente de la tabla.

**nota**  
Para `BatchPutItem`, es `ctx.result.data.unprocessedItems`. Para `BatchDeleteItem`, es `ctx.result.data.unprocessedKeys`.

Vamos a gestionar este error de tres formas diferentes.

#### 1. Pasar por alto el error de invocación
<a name="swallowing-the-invocation-error-js"></a>

Si se devuelven los datos sin gestionar el error de invocación se pasa por alto el error, lo que hace que el resultado para el campo de GraphQL indicado siempre tenga éxito.

El código que escribimos ya es conocido y solo se centra en los datos de resultados.

**Controlador de respuestas**

```
export function response(ctx) {
  return ctx.result.data
}
```

**Respuesta de GraphQL**

```
{
  "data": {
    "getReadings": [
      {
        "sensorId": "1",
        "timestamp": "2018-02-01T17:21:05.000+08:00",
        "lat": 47.615063,
        "long": -122.333551
      },
      {
        "sensorId": "1",
        "timestamp": "2018-02-01T17:21:05.000+08:00",
        "value": 85.5
      }
    ]
  }
}
```

No se añadirán errores a la respuesta, ya que solo se actúa en los datos.

#### 2. Se genera un error para abortar la ejecución del controlador de respuestas
<a name="raising-an-error-to-abort-the-response-execution-js"></a>

Cuando los errores parciales se deban tratar como errores completos desde la perspectiva del cliente, puede anular la ejecución del controlador de respuestas para evitar la devolución de datos. El método de utilidad `util.error(...)` consigue exactamente ese comportamiento.

**Código de controlador de respuestas**

```
export function response(ctx) {
  if (ctx.error) {
    util.error(ctx.error.message, ctx.error.type, null, ctx.result.data.unprocessedKeys);
  }
  return ctx.result.data;
}
```

**Respuesta de GraphQL**

```
{
  "data": {
    "getReadings": null
  },
  "errors": [
    {
      "path": [
        "getReadings"
      ],
      "data": null,
      "errorType": "DynamoDB:ProvisionedThroughputExceededException",
      "errorInfo": {
        "temperatureReadings": [
          {
            "sensorId": "1",
            "timestamp": "2018-02-01T17:21:05.000+08:00"
          }
        ],
        "locationReadings": []
      },
      "locations": [
        {
          "line": 58,
          "column": 3
        }
      ],
      "message": "You exceeded your maximum allowed provisioned throughput for a table or for one or more global secondary indexes. (...)"
    }
  ]
}
```

Aunque podrían haberse devuelto algunos resultados de la operación por lotes de DynamoDB, decidimos generar un error que hace que el campo de GraphQL `getReadings` sea nulo y se añada el error al bloque de *errors* de la respuesta de GraphQL.

#### 3. Añadir el error para devolver tanto los datos como los errores
<a name="appending-an-error-to-return-both-data-and-errors-js"></a>

En algunos casos, para proporcionar una mejor experiencia al usuario, las aplicaciones pueden devolver resultados parciales e informar a sus clientes de los elementos que no se han procesado. Los clientes pueden decidir probar de nuevo o trasladar el error al usuario final. El método `util.appendError(...)` es la utilidad que permite este comportamiento al permitir que el creador de la aplicación agregue errores al contexto sin interferir con la evaluación del controlador de respuestas. Tras evaluar el controlador de respuestas, AWS AppSync procesará los errores de contexto añadiéndolos al bloque de errores de la respuesta de GraphQL.

**Código de controlador de respuestas**

```
export function response(ctx) {
  if (ctx.error) {
    util.appendError(ctx.error.message, ctx.error.type, null, ctx.result.data.unprocessedKeys);
  }
  return ctx.result.data;
}
```

Hemos reenviado el error de invocación y el elemento `unprocessedKeys` dentro del bloque de errores de la respuesta de GraphQL. El campo `getReadings` también devuelve datos parciales de la tabla `locationReadings`, como puede ver en la respuesta a continuación.

**Respuesta de GraphQL**

```
{
  "data": {
    "getReadings": [
      null,
      {
        "sensorId": "1",
        "timestamp": "2018-02-01T17:21:05.000+08:00",
        "value": 85.5
      }
    ]
  },
  "errors": [
    {
      "path": [
        "getReadings"
      ],
      "data": null,
      "errorType": "DynamoDB:ProvisionedThroughputExceededException",
      "errorInfo": {
        "temperatureReadings": [
          {
            "sensorId": "1",
            "timestamp": "2018-02-01T17:21:05.000+08:00"
          }
        ],
        "locationReadings": []
      },
      "locations": [
        {
          "line": 58,
          "column": 3
        }
      ],
      "message": "You exceeded your maximum allowed provisioned throughput for a table or for one or more global secondary indexes. (...)"
    }
  ]
}
```

# Uso de resolutores HTTP en AWS AppSync
<a name="tutorial-http-resolvers-js"></a>

AWS AppSync le permite utilizar fuentes de datos compatibles (es decir, Amazon DynamoDB AWS Lambda, Amazon Service o Amazon Aurora) para realizar diversas operaciones, además de cualquier punto de enlace HTTP arbitrario para resolver los campos de GraphQL. OpenSearch Una vez que los puntos de enlace HTTP están disponibles, puede conectar con ellos mediante un origen de datos. A continuación, puede configurar un solucionador en el esquema para realizar operaciones de GraphQL como consultas, mutaciones, y suscripciones. Este tutorial le guiará a lo largo de algunos ejemplos comunes.

En este tutorial, utilizará una API REST (creada con Amazon API Gateway y Lambda) con un punto final de GraphQL AWS AppSync .

## Creación de una API de REST
<a name="creating-a-rest-api"></a>

Puede usar la siguiente AWS CloudFormation plantilla para configurar un punto final de REST que funcione para este tutorial:

[https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/http/http-api-gw.yaml](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/new?templateURL=https://s3.us-west-2.amazonaws.com/awsappsync/resources/http/http-api-gw.yaml)

La AWS CloudFormation pila realiza los siguientes pasos:

1. Configura una función Lambda que contiene la lógica de negocio del microservicio.

1. Configura una API REST de API Gateway con la siguiente combinación endpoint/method/content de tipos:


****  

| Ruta de recurso de la API | Método HTTP | Tipo de contenido admitido | 
| --- | --- | --- | 
|  /v1/users  |  POST  |  application/json  | 
|  /v1/users  |  GET  |  application/json  | 
|  /v1/users/1  |  GET  |  application/json  | 
|  /v1/users/1  |  PUT  |  application/json  | 
|  /v1/users/1  |  DELETE  |  application/json  | 

## Creación de la API de GraphQL
<a name="creating-your-graphql-api"></a>

Para crear la API GraphQL en: AWS AppSync

1. Abre la AWS AppSync consola y selecciona **Crear API**.

1. Elige **GraphQL APIs** y, a continuación, selecciona **Diseñar desde cero**. Elija **Siguiente**.

1. En el nombre de la API, introduzca `UserData`. Elija **Siguiente**.

1. Elija `Create GraphQL resources later`. Elija **Siguiente**.

1. Revise las entradas y seleccione **Crear API**.

La AWS AppSync consola crea una nueva API de GraphQL para usted mediante el modo de autenticación con clave de API. Puede utilizar la consola para configurar aún más la API de GraphQL y ejecutar solicitudes.

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

Ahora que tiene una API de GraphQL, vamos a crear un esquema de GraphQL. En el editor de **esquemas** de la AWS AppSync consola, usa el siguiente fragmento:

```
type Mutation {
    addUser(userInput: UserInput!): User
    deleteUser(id: ID!): User
}

type Query {
    getUser(id: ID): User
    listUser: [User!]!
}

type User {
    id: ID!
    username: String!
    firstname: String
    lastname: String
    phone: String
    email: String
}

input UserInput {
    id: ID!
    username: String!
    firstname: String
    lastname: String
    phone: String
    email: String
}
```

## Configure el origen de datos HTTP
<a name="configure-your-http-data-source"></a>

Para configurar el origen de datos HTTP, haga lo siguiente:

1. En la página **Fuentes de datos** de tu API de AWS AppSync GraphQL, selecciona **Crear fuente de datos**.

1. Escriba un nombre para el origen de datos como `HTTP_Example`.

1. En **Tipo de origen de datos**, elija **Punto de conexión HTTP**.

1. Establezca el punto de conexión en el punto de conexión de la puerta de enlace de API que se creó al principio del tutorial. Para encontrar el punto de conexión generado por la pila, vaya a la consola de Lambda y busque su aplicación en **Aplicaciones**. Dentro de la configuración de la aplicación, debería ver un **punto de conexión de API**, que será su punto de conexión en AWS AppSync. Asegúrese de no incluir el nombre de etapa como parte del punto de conexión. Por ejemplo, si su punto de conexión fuera `https://aaabbbcccd.execute-api.us-east-1.amazonaws.com/v1`, escribiría `https://aaabbbcccd.execute-api.us-east-1.amazonaws.com`.

**nota**  
En este momento, solo se admiten puntos finales públicos. AWS AppSync  
Para obtener más información sobre las autoridades de certificación reconocidas por el AWS AppSync servicio, consulte [Autoridades de certificación (CA) reconocidas AWS AppSync por los puntos de enlace HTTPS](http-cert-authorities.md#aws-appsync-http-certificate-authorities).

## Configuración de solucionadores
<a name="configuring-resolvers"></a>

En este paso conectará el origen de datos HTTP a las consultas `getUser` y `addUser`.

Para configurar el solucionador de `getUser`:

1. En tu API de AWS AppSync GraphQL, selecciona la pestaña **Esquema**.

1. A la derecha del editor **Esquema**, en el panel **Solucionadores** y en la sección de tipo **Consulta**, busque el campo `getUser` y seleccione **Asociar**.

1. Mantenga el tipo de solucionador en `Unit` y el tiempo de ejecución en `APPSYNC_JS`.

1. En **Nombre del origen de datos**, elija el punto de conexión HTTP que creó anteriormente.

1. Seleccione **Crear**.

1. En el editor de código **Solucionador**, añada el siguiente fragmento como controlador de solicitudes:

   ```
   import { util } from '@aws-appsync/utils'
   
   export function request(ctx) {
   	return {
   		version: '2018-05-29',
   		method: 'GET',
   		params: {
   			headers: {
   				'Content-Type': 'application/json',
   			},
   		},
   		resourcePath: `/v1/users/${ctx.args.id}`,
   	}
   }
   ```

1. Añada el siguiente fragmento como controlador de respuestas:

   ```
   export function response(ctx) {
   	const { statusCode, body } = ctx.result
   	// if response is 200, return the response
   	if (statusCode === 200) {
   		return JSON.parse(body)
   	}
   	// if response is not 200, append the response to error block.
   	util.appendError(body, statusCode)
   }
   ```

1. Elija la pestaña **Query (Consulta)** y ejecute la consulta siguiente:

   ```
   query GetUser{
       getUser(id:1){
           id
           username
       }
   }
   ```

   Debe obtener la siguiente respuesta:

   ```
   {
       "data": {
           "getUser": {
               "id": "1",
               "username": "nadia"
           }
       }
   }
   ```

Para configurar el solucionador de `addUser`:

1. Elija la pestaña **Schema (Esquema)**.

1. A la derecha del editor **Esquema**, en el panel **Solucionadores** y en la sección de tipo **Consulta**, busque el campo `addUser` y seleccione **Asociar**.

1. Mantenga el tipo de solucionador en `Unit` y el tiempo de ejecución en `APPSYNC_JS`.

1. En **Nombre del origen de datos**, elija el punto de conexión HTTP que creó anteriormente.

1. Seleccione **Crear**.

1. En el editor de código **Solucionador**, añada el siguiente fragmento como controlador de solicitudes:

   ```
   export function request(ctx) {
       return {
           "version": "2018-05-29",
           "method": "POST",
           "resourcePath": "/v1/users",
           "params":{
               "headers":{
                   "Content-Type": "application/json"
               },
           "body": ctx.args.userInput
           }
       }
   }
   ```

1. Añada el siguiente fragmento como controlador de respuestas:

   ```
   export function response(ctx) {
       if(ctx.error) {
           return util.error(ctx.error.message, ctx.error.type)
       }
       if (ctx.result.statusCode == 200) {
           return ctx.result.body
       } else {
           return util.appendError(ctx.result.body, "ctx.result.statusCode")
       }
   }
   ```

1. Elija la pestaña **Query (Consulta)** y ejecute la consulta siguiente:

   ```
   mutation addUser{
       addUser(userInput:{
           id:"2",
           username:"shaggy"
       }){
           id
           username
       }
   }
   ```

   Si vuelve a ejecutar la consulta `getUser`, debería devolver la siguiente respuesta:

   ```
   {
       "data": {
           "getUser": {
           "id": "2",
           "username": "shaggy"
           }
       }
   }
   ```

## Invocando servicios AWS
<a name="invoking-aws-services-js"></a>

Puedes usar resolutores HTTP para configurar una interfaz AWS API de GraphQL para los servicios. Las solicitudes HTTP AWS deben firmarse con el [proceso Signature Version 4](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html) para AWS poder identificar quién las envió. AWS AppSync calcula la firma en su nombre al asociar una función de IAM a la fuente de datos HTTP.

Usted proporciona dos componentes adicionales para invocar AWS servicios con resolutores HTTP:
+ Un rol de IAM con permisos para llamar al servicio AWS APIs
+ Configuración de la firma en el origen de datos

Por ejemplo, si desea llamar a la [ListGraphqlApis operación](https://docs.aws.amazon.com/appsync/latest/APIReference/API_ListGraphqlApis.html) con resolutores HTTP, primero debe [crear un rol de IAM](attaching-a-data-source.md#aws-appsync-getting-started-build-a-schema-from-scratch) que AWS AppSync asuma la siguiente política adjunta:

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

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Action": [
                "appsync:ListGraphqlApis"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }
    ]
}
```

------

A continuación, cree la fuente de datos HTTP para. AWS AppSync En este ejemplo, debe realizar una llamada a AWS AppSync en la región EE. UU. Oeste (Oregón). Ajuste la siguiente configuración HTTP en un archivo denominado `http.json`, que incluye la región de la firma y el nombre del servicio:

```
{
    "endpoint": "https://appsync.us-west-2.amazonaws.com/",
    "authorizationConfig": {
        "authorizationType": "AWS_IAM",
        "awsIamConfig": {
            "signingRegion": "us-west-2",
            "signingServiceName": "appsync"
        }
    }
}
```

A continuación, utilice AWS CLI para crear la fuente de datos con un rol asociado de la siguiente manera:

```
aws appsync create-data-source --api-id <API-ID> \
                               --name AWSAppSync \
                               --type HTTP \
                               --http-config file:///http.json \
                               --service-role-arn <ROLE-ARN>
```

Cuando adjunte una resolución al campo del esquema, utilice la siguiente plantilla de mapeo de solicitudes para llamar AWS AppSync:

```
{
    "version": "2018-05-29",
    "method": "GET",
    "resourcePath": "/v1/apis"
}
```

Cuando ejecutas una consulta GraphQL para esta fuente de datos, AWS AppSync firma la solicitud con el rol que has proporcionado e incluye la firma en la solicitud. La consulta devuelve una lista de AWS AppSync GraphQL APIs en tu cuenta en esa AWS región.

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