Hamburger Icon
AWS Crear una API con API Gateway y Lambda con Python

AWS Crear una API con API Gateway y Lambda con Python

¿Necesitas crear una API? En Amazon Web Services puedes crear una de forma relativamente sencilla, sin necesidad de pagar nada y con todas las funcionalidades que una API necesita.

En AWS podemos encontrar centenares de servicios diferentes y puede resultar abrumador, pero con un poco de paciencia domarás a la bestia y tendrás a tu disposición una capa gratuita muy útil tanto para proyectos personales como para proyectos reales.

Para este tutorial se da por supuesto que se tiene una cuenta con acceso a la consola de AWS. Si todavía no tienes una cuenta, solo tienes que dirigirte a su página y registrarte.

Estos son los diferentes servicios que vamos a usar para construir una API:

  • API Gateway: desde aquí podremos configurar la estructura de nuestra API.
  • Lambda: con este servicio crearemos una función serverless que contendrá la lógica que necesitamos, usando Python.
  • CloudWatch: debugar es esencial para entender cómo funciona todo, usaremos este servicio para ver qué sucede cuando hacemos llamadas a nuestra API y poder debugar posibles errores.

Aunque parezca que va a ser complicado, realmente no lo es tanto. Los diferentes servicios de AWS están muy bien integrados entre ellos, esto le da a la plataforma una flexibilidad y potencia enormes.

Antes de empezar

Si no has usado nunca AWS, familiarízate con la consola, curiosea algunos de los servicios que hay, mira las opciones. Para moverte entre servicios, lo más fácil es usar el menú de servicios de la zona superior o el propio buscador.

Revisa también qué zona geográfica tienes activa, arriba a la derecha tienes un desplegable que te permitirá seleccionar una zona, escoge la que más te convenga. En AWS, los servicios que creas en una zona no estarán por defecto disponibles en otra diferente, tenlo en cuenta.

Lo primero es crear nuestra función Lambda

Abre el servicio AWS Lambda y desde el menú de la izquierda, selecciona la opción Functions (o Funciones, si tienes la interfaz en español).

Por ahí debes de tener un botón para crear una nueva función. Hazle clic.

AWS Lambda menú de funciones

Configuración inicial

Indicaremos que queremos crear una función desde cero, sin ninguna plantilla ni nada. Le daremos un nombre a la función, yo en mi ejemplo la voy a llamar api-demo, ponle el nombre que quieras.

Como runtime vamos a escoger la última versión de Python disponible. El resto de opciones las dejaremos tal cual vienen marcadas por defecto. Una vez hecho esto, crearemos la función confirmando los datos.

Una vez hecho, veremos algo parecido a esto:

AWS Lambda vista general

Mi consejo es que vayas a la pestaña de configuración y revises todo lo que es posible personalizar en las funciones Lambda. Quizás más adelante te interese configurar variables de entorno o disparadores. Por ahora simplemente queremos familiarizarnos con las opciones que tenemos disponibles.

Modificando la función Lambda con Visual Studio Code

Desde la pestaña de código puedes modificar la función. Puedes poner el editor a pantalla completa y trabajar de forma bastante aceptable, pero ya te adelanto que no es lo mismo que usar tu editor habitual.

Si prefieres modificar tu código desde VS Code para sentirte como pez en el agua, necesitarás instalar la extensión AWS Toolkit.

Una vez instalada, en AWS tendrás que crear unas credenciales de acceso a tu cuenta. Documentación sobre cómo generar las claves.

Una vez tengas las credenciales, toca configurarlas en VS Code para que pueda conectar con tu cuenta de AWS. Documentación sobre cómo configurar claves en VS Code.

Haciendo pruebas de ejecución

Una vez en este punto, vamos a probar a ejecutar nuestra función lambda desde VS Code. Desde el menú de la extensión AWS Toolkit, añade la región de AWS donde se encuentra tu Lambda, en mi caso París:

VS Code AWS Toolkit añadir región

VS Code AWS Toolkit añadir región

Ahora debes de poder ver la función que has creado antes y puedes invocarla:

VS Code invocar AWS Lambda

En la pestaña que se abrirá podrás indicar los datos que le quieres pasar a la función e invocarla. Para nuestra prueba, simplemente ejecutamos sin pasarle nada y ya deberíamos ver el resultado:

VS Code invocar AWS Lambda

Descargar, modificar y actualizar la función

Para poder modificar la función primero tendremos que descargarla y editarla. Si haces clic derecho sobre la función, verás que a parte de invocarla en AWS también puedes descargarla. Descárgala en tu equipo y abre su código.

VS Code descargar AWS Lambda

Selecciona una ubicación y abre la carpeta que contiene el código en una nueva área de trabajo.

El archivo principal es lambda_funcion.py. Por ahora no hay más código que ese, pero eres libre de crear la estructura de directorios y ficheros que necesites.

Vamos a modificar el código para poder pasarle un parámetro a la función y que lo muestre en el resultado.

Los parámetros que pasaremos se recogen desde el parámetro de entrada event. Por ejemplo, puedes hacer algo así:

import json

def lambda_handler(event, context):

    # Recogemos datos de event, suponiendo
    # que pasaremos en el payload las variables
    # nombre y edad

    nombre = event.get('nombre')
    edad = event.get('edad')


    resultado = {
        'param_nombre': nombre,
        'param_edad': edad
    }


    return {
        'statusCode': 200,
        'body': json.dumps(resultado)
    }

Una vez tengamos esto, podemos subir los cambios a AWS:

VS Code actualizar AWS Lambda

Seleccionaremos el directorio que contiene nuestra función:

VS Code actualizar AWS Lambda

Luego simplemente indicaremos que queremos subir el directorio, sin ejecutar ningún comando build:

VS Code actualizar AWS Lambda

Y por último deberemos confirmar que queremos queremos publicar este código:

VS Code actualizar AWS Lambda

En unos segundos nuestra función Lambda se habrá actualizado. Si ahora la invocamos de nuevo, podremos pasarle los parámetros que necesita, en lo que sería el cuerpo de la petición REST:

VS Code invocar Lambda

Debugando nuestro código con AWS CloudWatch

Cuando algo salga mal, vas a querer ver qué está sucediendo para solucionar el problema. El registro de errores y de información va a ser crucial para poder avanzar. Vamos a poder consultar un log de ejecuciones a través del servicio CloudWatch.

Abre el servicio en la consola de AWS y busca en los grupos de logs. Ahí verás grupos por cada una de las funciones Lambda que tengas:

AWS CloudWatch

Si abres el grupo de logs de nuestra función, podrás ver registros de diferentes momentos de ejecución:

AWS CloudWatch

Dentro tendrás los detalles de esa ejecución y posibles errores que se hayan podido producir:

AWS CloudWatch

A esta información también puedes acceder desde VS Code, si te es más cómodo:

AWS CloudWatch en VS Code

AWS CloudWatch en VS Code

AWS CloudWatch en VS Code

Consejo sobre AWS Toolkit

Esta mecánica puede resultar un poco cansina, porque muchas veces vas a querer hacer un pequeño cambio y probarlo. Mi consejo es que hagas las pruebas en local y cuando ya tengas bastantes cambios acumulados, los subas a AWS.

Creando la estructura de nuestra API con AWS API Gateway

Una vez tengamos controlado cómo usar las funciones Lambda, tendremos que crear la estructura de nuestra API. Si abrimos el servicio API Gateway podremos crear una nueva API de tipo REST:

AWS API Gateway crear API REST

Tendremos que indicar que nuestra API será de tipo REST, que queremos crear una API nueva desde cero e indicar un nombre:

AWS API Gateway crear API REST

Para esta demo vamos a crear un solo recurso a la API con los métodos GET y POST diponibles.

Crearemos un recurso llamado demo de esta forma:

AWS API Gateway recurso

AWS API Gateway recurso

Ahora añadiremos los métodos GET y POST. Para ello haremos clic en el recurso demo que hemos creado y desde las acciones añadiremos los métodos:

AWS API Gateway métodos

AWS API Gateway métodos

Nos tiene que quedar algo así:

AWS API Gateway métodos

Lo siguiente será enlazar los dos métodos con la función Lambda. Esto lo podemos hacer haciendo clic en el método y añadiendo esta configuración (para los dos métodos será igual):

AWS API Gateway métodos

Es importante que marquemos la opción de integración proxy para que luego tengamos el control de la petición y la respuesta desde la función Lambda.

Finalmente, desplegar la API para que sea accesible a través de una URL:

AWS API Gateway despliegue

Tendremos que crear una nueva fase de despliegue, que podemos nombrar por ejemplo v1, para que en un futuro podamos o bien actualizar esta fase con nuevos métodos o crear una v2 en actualizaciones más grandes. También es una buena idea crear una fase test o similar para hacer pruebas antes de pasar los cambios a producción.

AWS API Gateway despliegue

En unos instantes tendremos ya disponible la URL a través de la que podremos usar la API REST:

AWS API Gateway despliegue

Siempre que hagamos cambios en la estructura de la API deberemos seguir estos pasos para hacer el despliegue y que estén disponibles públicamente.

Integración con AWS Lambda

Ahora tenemos que adaptar la función Lambda que hemos creado antes para poder recibir las peticiones a través de la API y responder como toque.

Para ello, nos será muy útil ver qué hay en el parámetro de entrada event cuando se llama a la función Lambda a través de la URL de invocación de la API.

Podemos hacer esto en nuestro código para verlo:

import json

def lambda_handler(event, context):

    print(json.dumps(event))

    return {
        "statusCode": 200,
        "body": ""
    }

Asegúrate de subir los cambios a AWS Lambda como hemos hecho antes y accede a la URL de invocación a través del navegador, sin olvidar añadir al final de la url /demo que es el recurso que hemos creado. El navegador lanzará una petición GET, aunque por pantalla no veamos nada.

Si ahora miras el registro de mensajes con AWS CloudWatch, verás que se ha impreso el contenido de la variable event, con un contenido similar a este:

{
  "resource": "/demo",
  "path": "/demo",
  "httpMethod": "GET",
  "headers": {
    "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
    "accept-encoding": "gzip, deflate, br",
    "accept-language": "es,es-ES;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,ca;q=0.5,it;q=0.4,fr;q=0.3",
    "Host": "xo16s41e58.execute-api.eu-west-3.amazonaws.com",
    "sec-ch-ua": "\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"98\", \"Microsoft Edge\";v=\"98\"",
    "sec-ch-ua-mobile": "?0",
    "sec-ch-ua-platform": "\"Windows\"",
    "sec-fetch-dest": "document",
    "sec-fetch-mode": "navigate",
    "sec-fetch-site": "none",
    "sec-fetch-user": "?1",
    "upgrade-insecure-requests": "1",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36 Edg/98.0.1108.43",
    "X-Amzn-Trace-Id": "Root=1-62068fc1-66564bab7c4b35002283f18b",
    "X-Forwarded-For": "139.47.33.38",
    "X-Forwarded-Port": "443",
    "X-Forwarded-Proto": "https"
  },
  "multiValueHeaders": {
    "accept": [
      "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
    ],
    "accept-encoding": ["gzip, deflate, br"],
    "accept-language": [
      "es,es-ES;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,ca;q=0.5,it;q=0.4,fr;q=0.3"
    ],
    "Host": ["xo16s41e58.execute-api.eu-west-3.amazonaws.com"],
    "sec-ch-ua": [
      "\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"98\", \"Microsoft Edge\";v=\"98\""
    ],
    "sec-ch-ua-mobile": ["?0"],
    "sec-ch-ua-platform": ["\"Windows\""],
    "sec-fetch-dest": ["document"],
    "sec-fetch-mode": ["navigate"],
    "sec-fetch-site": ["none"],
    "sec-fetch-user": ["?1"],
    "upgrade-insecure-requests": ["1"],
    "User-Agent": [
      "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36 Edg/98.0.1108.43"
    ],
    "X-Amzn-Trace-Id": ["Root=1-62068fc1-66564bab7c4b35002283f18b"],
    "X-Forwarded-For": ["139.47.33.38"],
    "X-Forwarded-Port": ["443"],
    "X-Forwarded-Proto": ["https"]
  },
  "queryStringParameters": null,
  "multiValueQueryStringParameters": null,
  "pathParameters": null,
  "stageVariables": null,
  "requestContext": {
    "resourceId": "dewsel",
    "resourcePath": "/demo",
    "httpMethod": "GET",
    "extendedRequestId": "NYtmPEueCGYFtNg=",
    "requestTime": "11/Feb/2022:16:33:05 +0000",
    "path": "/v1/demo",
    "accountId": "119867790837",
    "protocol": "HTTP/1.1",
    "stage": "v1",
    "domainPrefix": "xo16s41e58",
    "requestTimeEpoch": 1644597185376,
    "requestId": "0b246bfd-0f23-4b4d-93a3-2fef042b3e9e",
    "identity": {
      "cognitoIdentityPoolId": null,
      "accountId": null,
      "cognitoIdentityId": null,
      "caller": null,
      "sourceIp": "139.47.33.38",
      "principalOrgId": null,
      "accessKey": null,
      "cognitoAuthenticationType": null,
      "cognitoAuthenticationProvider": null,
      "userArn": null,
      "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36 Edg/98.0.1108.43",
      "user": null
    },
    "domainName": "xo16s41e58.execute-api.eu-west-3.amazonaws.com",
    "apiId": "xo16s41e58"
  },
  "body": null,
  "isBase64Encoded": false
}

A través del parámetro event podremos ver qué recurso se ha llamado desde la API con el valor de resource. Podremos ver el método usado en el valor de httpMethod. Desde body podremos recoger parámetros pasados por POST, mientras que desde queryStringParameters y pathParameters podremos acceder respectivamente a los parámetros de URL y de ruta.

Como puedes ver hay otros muchos detalles, como las cabeceras y demás. Para lo que queremos hacer, sabiendo el recurso y el método tendremos suficiente. Ahora mismo podemos modificar nuestra función Lambda de este modo:

import json

def lambda_handler(event, context):

    # print(json.dumps(event))

    recurso = event.get("resource")
    metodo = event.get("httpMethod")

    # si algun dato no se encuentra, devolver error 400
    if not recurso or not metodo:
        return {
            "statusCode": 400,
            "body": "Bad request"
        }

    # lógica principal de respuesta a las peticiones
    if recurso == "/demo":
        if metodo == "GET":
            return {
                "statusCode": 200,
                "body": "Has llamado al método GET de /demo"
            }

        if metodo == "POST":
            return {
                "statusCode": 200,
                "body": "Has llamado al método POST de /demo"
            }

De nuevo, tendremos que subir estos cambios a AWS Lambda. Ahora, si llamas a la API desde Postman o cualquier software de testeo de API's, podrás ver cómo responde según que tipo de petición hagas.

Petición GET:

AWS API Gateway pruebas

Petición POST:

AWS API Gateway pruebas

Con esto ya tendrías lista la base para desarrollar tu API.

Puede parecer complejo si es la primera vez que usas AWS, pero toda esta complejidad permite gran flexibilidad.

Hay muchos aspectos que han quedado fuera de este tutorial, como la seguridad de la API a través de autenticación o token, las limitaciones de uso, la configuración de CORS y un sinfín de cosas más, pero este post ya se me va de las manos, así que por mi salud mental mejor lo dejamos aquí por ahora.