{"id":7399,"date":"2021-04-28T15:26:23","date_gmt":"2021-04-28T13:26:23","guid":{"rendered":"http:\/\/www.unimedia.tech.mialias.net\/tiempo-real-con-serverless-usando-websocket-en-aws\/"},"modified":"2023-12-21T10:32:18","modified_gmt":"2023-12-21T09:32:18","slug":"tiempo-real-con-serverless-usando-websocket-en-aws","status":"publish","type":"post","link":"https:\/\/www.unimedia.tech\/es\/tiempo-real-con-serverless-usando-websocket-en-aws\/","title":{"rendered":"Tiempo real con serverless usando Websocket en AWS"},"content":{"rendered":"\n<p>Hola a todos, As\u00ed que hoy vamos a discutir sobre la implementaci\u00f3n de websocket con serverless mediante el uso de Node.js con Typescript para la comunicaci\u00f3n en tiempo real.<\/p>\n\n<p><\/p>\n\n<p>Si intenta buscarlo en Internet, encontrar\u00e1 fragmentos de detalles de <a href=\"https:\/\/www.unimedia.tech\/dedicated-development-teams\/\">aplicaci\u00f3n <\/a>, pero no hay nada concreto. As\u00ed que, aqu\u00ed estoy construyendo un ecosistema websocket completo con todo el c\u00f3digo y detalles de configuraci\u00f3n.<\/p>\n\n<p><\/p>\n\n<p>Para el <a href=\"https:\/\/www.unimedia.tech\/software-development\/\">desarrollo<\/a>, necesita tener <code><strong>serverless<\/strong><\/code>, <code><strong>npm<\/strong><\/code> y\/o <code><strong>yarn<\/strong><\/code> instalados globalmente. Vea c\u00f3mo <a href=\"https:\/\/classic.yarnpkg.com\/en\/docs\/install\/\">instalar YARN<\/a> y <a href=\"https:\/\/docs.npmjs.com\/downloading-and-installing-node-js-and-npm\">NPM<\/a>. Tambi\u00e9n prefiero usar <a href=\"https:\/\/github.com\/nvm-sh\/nvm#installing-and-updating\">NVM<\/a> para gestionar mis versiones de Node.<\/p>\n\n<p><\/p>\n\n<h2 class=\"wp-block-heading\">Desarrollo<\/h2>\n\n<p><\/p>\n\n<h3 class=\"wp-block-heading\" id=\"4fcc\">Paso 1. Configurar AWS<\/h3>\n\n<p id=\"a6a6\">Configurar AWS CLI si a\u00fan no lo ha hecho, hay un art\u00edculo muy bueno aqu\u00ed en <a href=\"https:\/\/sidneyb231.medium.com\/configure-aws-for-development-and-deployment-ad822097fc22\">crear y configurar las credenciales de AWS<\/a>. A continuaci\u00f3n, tenemos que configurar nuestra aplicaci\u00f3n.<\/p>\n\n<p><\/p>\n\n<p><\/p>\n\n<h3 class=\"wp-block-heading\" id=\"b92d\">Segundo paso. Configurar proyecto<\/h3>\n\n<p id=\"7031\">Crear y configurar un proyecto serverless con typescript:<\/p>\n\n<pre class=\"wp-block-code\"><code>$ sls create --template aws-nodejs-typescript --path &lt;PROJECT-NAME&gt;<\/code><\/pre>\n\n<p id=\"3e73\">Donde <code><strong>&lt;PROJECT-NAME&gt;<\/strong><\/code> es el nombre de su proyecto.<\/p>\n\n<p id=\"f6f1\">Esto genera un serverless boilerplate para nuestra aplicaci\u00f3n. A continuaci\u00f3n, tenemos que navegar a nuestro nuevo proyecto <code>cd &lt;PROJECT-NAME&gt;<\/code>e instalar nuestras dependencias ejecutando <code><strong>yarn install<\/strong><\/code>.<\/p>\n\n<p id=\"52c5\">Los archivos m\u00e1s importantes que actualizaremos a medida que desarrollemos nuestra aplicaci\u00f3n son <code><strong>handler.ts<\/strong><\/code> y <code><strong>serverless.ts<\/strong><\/code>. El archivo <code><strong>handler.ts<\/strong><\/code> maneja nuestras funciones lambda o referencias a nuestras funciones lambda. Deber\u00eda verse as\u00ed:<\/p>\n\n<pre class=\"wp-block-code\"><code>import { APIGatewayProxyHandler } from 'aws-lambda';\nimport 'source-map-support\/register';\n\nexport const hello: APIGatewayProxyHandler = async (event, _context) =&gt; {\n  return {\n    statusCode: 200,\n    body: JSON.stringify({\n      message: 'Go Serverless Webpack (Typescript) v1.0! Your function executed successfully!',\n      input: event,\n    }, null, 2),\n  };\n}<\/code><\/pre>\n\n<p><\/p>\n\n<p><\/p>\n\n<p>El archivo serverless.ts nos proporciona una plantilla para modelar y aprovisionar los recursos de nuestra aplicaci\u00f3n para AWS CloudFormation tratando la infraestructura como c\u00f3digo. Esto nos ayuda a crear, actualizar e incluso eliminar recursos f\u00e1cilmente. Deber\u00eda verse as\u00ed:<\/p>\n\n<pre class=\"wp-block-code\"><code>import type { Serverless } from 'serverless\/aws';\n\nconst serverlessConfiguration: Serverless = {\n  service: {\n    name: 'serverless-websocket-ts',\n    \/\/ app and org for use with dashboard.serverless.com\n    \/\/ app: your-app-name,\n    \/\/ org: your-org-name,\n  },\n  frameworkVersion: '&gt;=1.72.0',\n  custom: {\n    webpack: {\n      webpackConfig: '.\/webpack.config.js',\n      includeModules: true\n    }\n  },\n  \/\/ Add the serverless-webpack plugin\n  plugins: &#91;'serverless-webpack'],\n  provider: {\n    name: 'aws',\n    runtime: 'nodejs12.x',\n    apiGateway: {\n      minimumCompressionSize: 1024,\n    },\n    environment: {\n      AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1',\n    },\n  },\n  functions: {\n    hello: {\n      handler: 'handler.hello',\n      events: &#91;\n        {\n          http: {\n            method: 'get',\n            path: 'hello',\n          }\n        }\n      ]\n    }\n  }\n}\n\nmodule.exports = serverlessConfiguration;<\/code><\/pre>\n\n<p><\/p>\n\n<p id=\"16b6\">Adem\u00e1s, debido a <a href=\"https:\/\/www.serverless.com\/framework\/docs\/deprecations\/\">las depreciaciones<\/a>, tendr\u00e1 que establecer la propiedad de servicio directamente al nombre del servicio:<\/p>\n\n<pre class=\"wp-block-code\"><code>{ \n  service: 'serverless-websocket-ts',\n  ...\n}<\/code><\/pre>\n\n<p><\/p>\n\n<p id=\"ed9f\">Y actualice el objeto <code><strong>provider.apiGateway<\/strong><\/code> de la siguiente manera:<\/p>\n\n<pre class=\"wp-block-code\"><code>provider: {<br>  ...<br>  apiGateway: {<br>    shouldStartNameWithService: true,<br>    ...<br>  },<br>  ...<br>},<\/code><\/pre>\n\n<p id=\"e05c\">Podemos ejecutar nuestra funci\u00f3n en la ra\u00edz de nuestro proyecto <code><strong>$ serverless invoke local --function hello<\/strong><\/code> y deber\u00edas ver la respuesta:<\/p>\n\n<pre class=\"wp-block-code\"><code>{\n    \"statusCode\": 200,\n    \"body\": \"{n  \"message\": \"Go Serverless Webpack (Typescript) v1.0! Your function executed           successfully!\",n  \"input\": \"n}\"\n}<\/code><\/pre>\n\n<p><\/p>\n\n<p id=\"59bf\">El framework sin servidor invoca localmente la funci\u00f3n <code>hello<\/code> y ejecuta el m\u00e9todo exportado <code><strong>hello<\/strong><\/code> en el archivo <code><strong>handler.ts<\/strong><\/code>. El comando <code>serverless invoke local<\/code> nos permite ejecutar nuestras funciones Lambda localmente antes de desplegarlas.<\/p>\n\n<p id=\"7be0\">A continuaci\u00f3n, tenemos que instalar algunas dependencias para nuestra aplicaci\u00f3n:<\/p>\n\n<pre class=\"wp-block-code\"><code>$ yarn add -D serverless-offline serverless-dotenv-plugin serverless-bundle<br>$ yarn add aws-sdk uuid @types\/uuid<\/code><\/pre>\n\n<ul class=\"wp-block-list\"><li><a href=\"https:\/\/www.npmjs.com\/package\/aws-sdk\">\n  <strong>aws-sdk<\/strong>\n<\/a> &#8211; nos permite comunicarnos con los servicios de AWS<\/li><li><a href=\"https:\/\/www.npmjs.com\/package\/uuid\"><strong>uuid<\/strong> <\/a>&#8211; genera identificadores \u00fanicos para las entradas de nuestra base de datos<\/li><li><a href=\"https:\/\/www.npmjs.com\/package\/serverless-offline\">\n  <strong>serverless-offline<\/strong>\n<\/a>&#8211; este plugin nos permite ejecutar nuestra aplicaci\u00f3n y funciones Lambda localmente<\/li><li><a href=\"https:\/\/www.npmjs.com\/package\/serverless-dotenv-plugin\">\n  <strong>serverless-dotenv-plugin<\/strong>\n<\/a> &#8211; para permitirnos cargar variables <code><strong>.env<\/strong><\/code> en nuestro entorno Lambda<\/li><li><a href=\"https:\/\/www.npmjs.com\/package\/serverless-bundle\"><strong>serverless-bundle<\/strong> <\/a>&#8211; este plugin empaqueta de forma \u00f3ptima nuestras funciones Typescript y garantiza que no tengamos que preocuparnos de instalar <a href=\"https:\/\/babeljs.io\/\">\n  <strong>Babel<\/strong>\n<\/a>, <a href=\"https:\/\/www.typescriptlang.org\/\">\n  <strong>Typescript<\/strong>\n<\/a>, <a href=\"https:\/\/webpack.js.org\/\">\n  <strong>Webpack<\/strong>\n<\/a>, <a href=\"https:\/\/eslint.org\/\">\n  <strong>ESLint<\/strong>\n<\/a> y muchos otros paquetes. Esto significa que no necesitamos mantener nuestras propias configuraciones de webpack. As\u00ed que podemos seguir adelante y eliminar el archivo <code><strong>webpack.config.js<\/strong><\/code> y la referencia a ella en <code>serverless.ts<\/code> propiedad personalizada:<\/li><\/ul>\n\n<pre class=\"wp-block-code\"><code>\/\/ FROM<br>custom: {<br>  webpack: {<br>    webpackConfig: '.\/webpack.config.js',<br>    includeModules: true<br>  }<br>}\/\/ TO<br>custom: {<br>  <br>}<\/code><\/pre>\n\n<p><\/p>\n\n<p><\/p>\n\n<h3 class=\"wp-block-heading\">Paso 3. Recursos de configuraci\u00f3n<\/h3>\n\n<p>Ahora necesitamos configurar las variables <code><strong>region<\/strong><\/code>, <code><strong>stage<\/strong><\/code>, <code><strong>table_throughputs<\/strong><\/code>, <code><strong>table_throughput<\/strong><\/code> y <strong>connections_table <\/strong>. Tambi\u00e9n configura <code><strong>dynamodb<\/strong><\/code> y <code><strong>serverless-offline<\/strong><\/code> para el desarrollo local.<\/p>\n\n<p><\/p>\n\n<pre class=\"wp-block-code\"><code>import type { Serverless } from 'serverless\/aws';\n\nconst serverlessConfiguration: Serverless = {\n  service: 'serverless-todo',\n  frameworkVersion: '&gt;=1.72.0',\n  custom: {\n    region: '${opt:region, self:provider.region}',\n    stage: '${opt:stage, self:provider.stage}',\n    connections_table: '${self:service}-connections-table-${opt:stage, self:provider.stage}',\n    table_throughputs: {\n      prod: 5,\n      default: 1,\n    },\n    table_throughput: '${self:custom.TABLE_THROUGHPUTS.${self:custom.stage}, self:custom.table_throughputs.default}',\n    dynamodb: {\n      stages: &#91;'dev'],\n      start: {\n        port: 8008,\n        inMemory: true,\n        heapInitial: '200m',\n        heapMax: '1g',\n        migrate: true,\n        seed: true,\n        convertEmptyValues: true,\n        \/\/ Uncomment only if you already have a DynamoDB running locally\n        \/\/ noStart: true\n      }\n    },\n    &#91;'serverless-offline']: {\n      httpPort: 3000,\n      babelOptions: {\n        presets: &#91;\"env\"]\n      }\n    }\n  },\n  plugins: &#91;\n      'serverless-bundle',\n      'serverless-offline',\n      'serverless-dotenv-plugin',\n  ],\n  package: {\n    individually: true,\n  },\n  provider: {\n    name: 'aws',\n    runtime: 'nodejs12.x',\n    stage: 'dev',\n    region: 'eu-west-1',\n    apiGateway: {\n      shouldStartNameWithService: true,\n      minimumCompressionSize: 1024,\n    },\n    environment: {\n      AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1',\n    },\n  },\n  functions: {\n    hello: {\n      handler: 'handler.hello',\n      events: &#91;\n        {\n          http: {\n            method: 'get',\n            path: 'hello',\n          }\n        }\n      ]\n    }\n  },\n  \n}\n\nmodule.exports = serverlessConfiguration;<\/code><\/pre>\n\n<p><\/p>\n\n<p id=\"7fde\">A continuaci\u00f3n, tenemos que actualizar la propiedad <code><strong>provider.environment<\/strong><\/code> en el archivo<code><strong>serverless.ts<\/strong><\/code> de la siguiente manera:<\/p>\n\n<pre class=\"wp-block-code\"><code>\/\/ serverless.ts\nprovider: {\n  ...\n  environment: {\n    ...\n    REGION: '${self:custom.region}',\n    STAGE: '${self:custom.stage}',\n    CONNECTIONS_TABLE: '${self:custom.connections_table}',\n  },\n  ...\n},<\/code><\/pre>\n\n<p><\/p>\n\n<p>A continuaci\u00f3n, tenemos que a\u00f1adir <code><strong>resources<\/strong><\/code> que se a\u00f1aden a su pila CloudFormation cuando se despliega nuestra aplicaci\u00f3n. Necesitamos definir los recursos de AWS en una propiedad titulada <code><strong>resources<\/strong><\/code>.  <\/p>\n\n<p>Puede obtener m\u00e1s informaci\u00f3n sobre c\u00f3mo <a href=\"https:\/\/docs.aws.amazon.com\/amazondynamodb\/latest\/developerguide\/WorkingWithTables.html\">trabajar con tablas y datos en DynamoDB<\/a>.<\/p>\n\n<p id=\"b50e\">Utilizando el poder de Javascript, podemos evitar grandes archivos de definici\u00f3n de servicios dividi\u00e9ndolos en archivos y utilizando importaciones din\u00e1micas. Esto es importante porque a medida que nuestra aplicaci\u00f3n crece, los archivos de definici\u00f3n separados facilitan el mantenimiento.<\/p>\n\n<p id=\"e53c\">Para este proyecto, organizaremos nuestros recursos por separado y los importaremos a <code><strong>serverless.ts<\/strong><\/code>. Para ello, primero debemos crear un directorio <code><strong>resources<\/strong><\/code> en nuestro directorio ra\u00edz y, a continuaci\u00f3n, crear un archivo <code><strong>dynamodb-tables.ts<\/strong><\/code> para nuestras tablas de DynamoDB:<\/p>\n\n<pre class=\"wp-block-code\"><code>\/\/ At project root\n$ touch resources\/dynamodb-tables.ts <\/code><\/pre>\n\n<p>A continuaci\u00f3n, actualizamos el archivo <code><strong>dynamodb-tables.ts<\/strong><\/code> del siguiente modo:<\/p>\n\n<p><\/p>\n\n<pre class=\"wp-block-code\"><code>export default {\n    ConnectionsTable: {\n        Type: 'AWS::DynamoDB::Table',\n        Properties: {\n            TableName: '${self:provider.environment.CONNECTIONS_TABLE}',\n            AttributeDefinitions: &#91;\n                { AttributeName: 'connectionId', AttributeType: 'S' }\n            ],\n            KeySchema: &#91;\n                { AttributeName: 'connectionId', KeyType: 'HASH' }\n            ],\n            ProvisionedThroughput: {\n                ReadCapacityUnits: '${self:custom.table_throughput}',\n                WriteCapacityUnits: '${self:custom.table_throughput}'\n            },\n            SSESpecification: {\n                SSEEnabled: true\n            },\n            TimeToLiveSpecification: {\n                AttributeName: 'ttl',\n                Enabled: true\n            }\n        }\n    },\n}<\/code><\/pre>\n\n<p><\/p>\n\n<p>E importar en <code><strong>serverless.ts<\/strong><\/code> &#8211; y establecer en las propiedades de los recursos como se indica a continuaci\u00f3n,<\/p>\n\n<pre class=\"wp-block-code\"><code>\/\/ DynamoDB\nimport dynamoDbTables from '.\/resources\/dynamodb-tables';\n\nconst serverlessConfiguration: Serverless = {\n  service: 'serverless-todo',\n  ...\n  resources: {\n    Resources: dynamoDbTables,\n  }\n  ...\n}\n\nmodule.exports = serverlessConfiguration;<\/code><\/pre>\n\n<p><\/p>\n\n<p id=\"bc8b\">Para ejecutar DynamoDB localmente, primero debemos instalar el complemento <code><strong>serverless-dynamodb-local<\/strong><\/code>:<\/p>\n\n<pre class=\"wp-block-code\"><code>$ yarn add -D serverless-dynamodb-local <\/code><\/pre>\n\n<p id=\"1d82\">A continuaci\u00f3n, debemos actualizar la matriz de plugins <code><strong>serverless.ts<\/strong><\/code>:<\/p>\n\n<pre class=\"wp-block-code\"><code>plugins: &#91;<br>    'serverless-bundle',<br>    'serverless-dynamodb-local',<br>    'serverless-offline',<br>    'serverless-dotenv-plugin',<br>],<\/code><\/pre>\n\n<p id=\"f80b\">Para utilizar el complemento, debemos instalar DynamoDB Local ejecutando <code><strong>sls dynamodb install<\/strong><\/code> en la ra\u00edz del proyecto. Ejecutando<code><strong>sls dynamodb start<\/strong><\/code> se iniciar\u00e1 localmente:<\/p>\n\n<p><\/p>\n\n<p><\/p>\n\n<h3 class=\"wp-block-heading\">Paso 4. Integraci\u00f3n de Websocket<\/h3>\n\n<p>En este paso, vamos a crear un manejador de socket web, para conectar con los eventos de websocket.<\/p>\n\n<p>Puedes consultar aqu\u00ed en detalle los <a href=\"https:\/\/www.serverless.com\/framework\/docs\/providers\/aws\/events\/websocket\/\" target=\"_blank\" rel=\"noreferrer noopener\">eventos<\/a> websocket.<\/p>\n\n<pre class=\"wp-block-code\"><code>\/\/ create a websocket folder\nmkdir websocket\n\ncd websocket\ntouch handler.ts\ntouch index.ts\ntouch schemas.ts\ntouch broadcast.ts<\/code><\/pre>\n\n<p><\/p>\n\n<p><strong>handler.ts<\/strong> ser\u00e1 como se indica a continuaci\u00f3n:<\/p>\n\n<pre class=\"wp-block-code\"><code>\n\/\/ handler.ts\n\nimport type { ValidatedEventAPIGatewayProxyEvent } from '@libs\/apiGateway';\nimport { formatJSONResponse } from '@libs\/apiGateway';\nimport { middyfy } from '@libs\/lambda';\nimport * as AWS from 'aws-sdk';\nimport { getAllConnections, sendMessage } from '.\/broadcast';\nimport schema from '.\/schema';\n\nconst config: any = { region: \"us-east-1\" };\nif (process.env.STAGE === process.env.DYNAMODB_LOCAL_STAGE) {\n  config.accessKeyId = process.env.DYNAMODB_LOCAL_ACCESS_KEY_ID;\n  config.secretAccessKey = process.env.DYNAMODB_LOCAL_SECRET_ACCESS_KEY;\n  config.endpoint = process.env.DYNAMODB_LOCAL_ENDPOINT;\n}\n\nAWS.config.update(config);\n\nconst dynamodb = new AWS.DynamoDB.DocumentClient();\n\nconst connectionTable = process.env.CONNECTIONS_TABLE;\n\nconst websocketHandler: ValidatedEventAPIGatewayProxyEvent&lt;typeof schema&gt; = async (event) =&gt; {\n  console.log(event);\n  const { body, requestContext: { connectionId, routeKey }} = event;\n  console.log(body, routeKey, connectionId);\n  \n  switch (routeKey) {\n    case '$connect':\n      await dynamodb.put({\n        TableName: connectionTable,\n        Item: {\n          connectionId,\n          \/\/ Expire the connection an hour later. This is optional, but recommended.\n          \/\/ You will have to decide how often to time out and\/or refresh the ttl.\n          ttl: parseInt((Date.now() \/ 1000).toString() + 3600)\n        }\n      }).promise();\n      break;\n\n    case '$disconnect':\n      await dynamodb.delete({\n        TableName: connectionTable,\n        Key: { connectionId }\n      }).promise();\n      break;\n\n    case '$default':\n    default:\n      const connections = await getAllConnections();\n      await Promise.all(\n        connections.map(connectionId =&gt; sendMessage(connectionId, body))\n      );\n      break;\n  }\n\n  return formatJSONResponse({ statusCode: 200 });\n}\n\nexport const wsHandler = middyfy(websocketHandler);\n<\/code><\/pre>\n\n<p>La API-Gateway proporciona <a href=\"https:\/\/docs.aws.amazon.com\/apigateway\/latest\/developerguide\/apigateway-websocket-api-overview.html\">4 tipos de rutas<\/a> relacionadas con el ciclo de vida de un cliente ws:<\/p>\n\n<figure class=\"wp-block-table\"><table class=\"has-background\" style=\"background-color:#e7f5fe\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Evento<\/th><th>Acci\u00f3n<\/th><\/tr><\/thead><tbody><tr><td class=\"has-text-align-left\" data-align=\"left\"><strong>Conectar<\/strong><\/td><td>llamada a la conexi\u00f3n de un cliente ws<\/td><\/tr><tr><td class=\"has-text-align-left\" data-align=\"left\"><strong>$desconectar<\/strong><\/td><td>se ejecuta al desconectar un cliente ws (puede que no se ejecute en algunas situaciones)<\/td><\/tr><tr><td class=\"has-text-align-left\" data-align=\"left\"><strong>$por defecto<\/strong><\/td><td>si no hay ning\u00fan controlador para el evento<\/td><\/tr><tr><td class=\"has-text-align-left\" data-align=\"left\"><custom-route><\/custom-route><\/td><td>si se especifica el nombre de la ruta para un gestor<\/td><\/tr><\/tbody><\/table><\/figure>\n\n<p>Bas\u00e1ndonos en eso, hemos creado un switch case, donde cada caso har\u00e1 la acci\u00f3n apropiada relacionada con ese evento.<\/p>\n\n<figure class=\"wp-block-table\"><table class=\"has-background\" style=\"background-color:#e7f5fe\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Evento<\/th><th>Acci\u00f3n<\/th><\/tr><\/thead><tbody><tr><td class=\"has-text-align-left\" data-align=\"left\"><strong>Conectar<\/strong><\/td><td>crear\u00e1 una entrada en <em>connections-table<\/em>, con connectionId &amp; ttl.<\/td><\/tr><tr><td class=\"has-text-align-left\" data-align=\"left\"><strong>$desconectar<\/strong><\/td><td>eliminar\u00e1 la conexi\u00f3n de<em> la tabla de conexiones de<\/em> dynamodb.<\/td><\/tr><tr><td class=\"has-text-align-left\" data-align=\"left\"><strong>$por defecto<\/strong><\/td><td>transmitir\u00e1 los datos a todas las conexiones.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n<p><strong>schema.<\/strong> ts se encargar\u00e1 de validar el esquema.<\/p>\n\n<pre class=\"wp-block-code\"><code>\/\/ schema.ts\n\nexport default {\n  type: \"object\",\n  properties: {\n    name: { type: 'string' }\n  },\n  required: &#91;'name']\n} as const;<\/code><\/pre>\n\n<p><\/p>\n\n<p><\/p>\n\n<p><strong>index.<\/strong> ts mapear\u00e1 los eventos websocket con handler:<\/p>\n\n<pre class=\"wp-block-code\"><code>\/\/ index.ts\n\nimport { handlerPath } from '@libs\/handlerResolver';\n\nexport const wsHandler = {\n  handler: `${handlerPath(__dirname)}\/handler.wsHandler`,\n  events: &#91;\n    {\n      websocket: '$connect'\n    },\n    {\n      websocket: '$disconnect',\n    },\n    {\n      websocket: '$default'\n    }\n  ]\n};<\/code><\/pre>\n\n<p><\/p>\n\n<p>Necesitamos registrar <strong>\n  <em>wsHandler <\/em>\n<\/strong>export en<em> src\/functions\/index.ts<\/em><\/p>\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/functions\/index.ts\n\nexport { default as hello } from '.\/hello';\nexport { wsHandler } from '.\/websocket';\n<\/code><\/pre>\n\n<p><\/p>\n\n<p><strong>broadcast.ts<\/strong> contiene 2 funciones importantes como se indica a continuaci\u00f3n<\/p>\n\n<pre class=\"wp-block-code\"><code>import * as AWS from 'aws-sdk';\n\nconst config: any = { region: \"us-east-1\" };\nif (process.env.STAGE === process.env.DYNAMODB_LOCAL_STAGE) {\n    config.accessKeyId = process.env.DYNAMODB_LOCAL_ACCESS_KEY_ID;\n    config.secretAccessKey = process.env.DYNAMODB_LOCAL_SECRET_ACCESS_KEY;\n    config.endpoint = process.env.DYNAMODB_LOCAL_ENDPOINT;\n}\n\nAWS.config.update(config);\n\nconst dynamodb = new AWS.DynamoDB.DocumentClient();\n\nconst connectionTable = process.env.CONNECTIONS_TABLE;\n\nexport async function sendMessage(connectionId, body) {\n    try {\n        const endpoint = process.env.APIG_ENDPOINT;\n        const apig = new AWS.ApiGatewayManagementApi({\n            apiVersion: '2018-11-29',\n            endpoint\n        });\n        await apig.postToConnection({\n            ConnectionId: connectionId,\n            Data: JSON.stringify(body)\n        }).promise();\n    } catch (err) {\n        \/\/ Ignore if connection no longer exists\n        if (err.statusCode !== 400 &amp;&amp; err.statusCode !== 410) {\n            throw err;\n        }\n    }\n}\n\nexport async function getAllConnections() {\n    const { Items, LastEvaluatedKey } = await dynamodb.scan({\n        TableName: connectionTable,\n        AttributesToGet: &#91;'connectionId']\n    }).promise();\n\n    const connections = Items.map(({ connectionId }) =&gt; connectionId);\n    if (LastEvaluatedKey) {\n        connections.push(...await getAllConnections());\n    }\n\n    return connections;\n}<\/code><\/pre>\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Funci\u00f3n<\/th><th>Explicaci\u00f3n<\/th><\/tr><\/thead><tbody><tr><td>enviarMensaje<\/td><td>acepta <strong>\n  <em>connectionId <\/em>\n<\/strong>&amp; <em>\n  <strong>cuerpo <\/strong>\n<\/em>como par\u00e1metros. Lee APIG_ENDPOINT de la variable de entorno y crea una instancia de <strong>ApiGatewayManagementApi<\/strong> y luego env\u00eda el cuerpo al <em>connectionId <\/em>dado usando <strong>postToConnection<\/strong>.<\/td><\/tr><tr><td>getAllConnections<\/td><td>devuelve todas las conexiones<\/td><\/tr><\/tbody><\/table><\/figure>\n\n<p><\/p>\n\n<h3 class=\"wp-block-heading\">Paso 4. Ejecuci\u00f3n local de Websocket<\/h3>\n\n<p>En este paso, ejecutaremos websocket localmente, usando serverless offline:<\/p>\n\n<p>Antes de empezar, compare y confirme el archivo serverless.ts como se indica a continuaci\u00f3n<\/p>\n\n<pre class=\"wp-block-code\"><code>\/\/ serverless.ts\n\nimport type { AWS } from '@serverless\/typescript';\n\nimport hello from '@functions\/hello';\nimport { wsHandler } from '@functions\/websocket';\nimport dynamoDbTables from '.\/dynamodb-tables';\n\n\nconst serverlessConfiguration: AWS = {\n  service: 'serverless-websocket-ts',\n  frameworkVersion: '2',\n  custom: {\n    region: '${opt:region, self:provider.region}',\n    stage: '${opt:stage, self:provider.stage}',\n    prefix: '${self:service}-${self:custom.stage}',\n    connections_table: '${self:service}-connections-table-${opt:stage, self:provider.stage}',\n    &#91;'serverless-offline']: {\n      httpPort: 3000,\n    },\n    &#91;'bundle']: {\n      linting: false\n    },\n    table_throughputs: {\n      prod: 5,\n      default: 1,\n    },\n    table_throughput: '${self:custom.TABLE_THROUGHPUTS.${self:custom.stage}, self:custom.table_throughputs.default}',\n    dynamodb: {\n      stages: &#91;'dev'],\n      start: {\n        port: 8008,\n        inMemory: true,\n        heapInitial: '200m',\n        heapMax: '1g',\n        migrate: true,\n        seed: true,\n        convertEmptyValues: true,\n        \/\/ Uncomment only if you already have a DynamoDB running locally\n        \/\/ noStart: true\n      }\n    }\n  },\n  plugins: &#91;\n    'serverless-bundle',\n    'serverless-dynamodb-local',\n    'serverless-offline',\n    'serverless-dotenv-plugin',\n  ],\n  package: {\n    individually: true,\n  },\n  provider: {\n    name: 'aws',\n    runtime: 'nodejs14.x',\n    apiGateway: {\n      minimumCompressionSize: 1024,\n      shouldStartNameWithService: true,\n    },\n    environment: {\n      AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1',\n      REGION: '${self:custom.region}',\n      STAGE: '${self:custom.stage}',\n      APIG_ENDPOINT: 'http:\/\/localhost:3001',\n      CONNECTIONS_TABLE: '${self:custom.connections_table}',\n    },\n    lambdaHashingVersion: '20201221',\n  },\n  \/\/ import the function via paths\n  functions: { \n    hello,\n    wsHandler\n  },\n\n  resources: {\n    Resources: dynamoDbTables,\n  }\n};\n\nmodule.exports = serverlessConfiguration;\n<\/code><\/pre>\n\n<pre class=\"wp-block-code\"><code>$ serverless offline start\n\nServerless: Bundling with Webpack...\nServerless: Watching for changes...\nDynamodb Local Started, Visit: http:\/\/localhost:8008\/shell\nIssues checking in progress...\nNo issues found.\nServerless: DynamoDB - created table serverless-websocket-ts-connections-table-dev\noffline: Starting Offline: dev\/us-east-1.\noffline: Offline &#91;http for lambda] listening on http:\/\/localhost:3002\noffline: Function names exposed for local invocation by aws-sdk:\n           * hello: serverless-websocket-ts-dev-hello\n           * wsHandler: serverless-websocket-ts-dev-wsHandler   \noffline: route '$connect'\noffline: route '$disconnect'\noffline: route '$default'\noffline: Offline &#91;websocket] listening on ws:\/\/localhost:3001\n\n   \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n   \u2502                                                                         \u2502\n   \u2502   POST | http:\/\/localhost:3000\/dev\/hello                                \u2502\n   \u2502   POST | http:\/\/localhost:3000\/2015-03-31\/functions\/hello\/invocations   \u2502\n   \u2502                                                                         \u2502\n   \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\noffline: Offline &#91;http for websocket] listening on http:\/\/localhost:3001\noffline: &#91;HTTP] server ready: http:\/\/localhost:3000  \noffline:\noffline: Enter \"rp\" to replay the last request\n<\/code><\/pre>\n\n<p><\/p>\n\n<p><\/p>\n\n<h3 class=\"wp-block-heading\">Paso 5: Demostraci\u00f3n de Websocket<\/h3>\n\n<p><\/p>\n\n<p><\/p>\n\n<figure class=\"wp-block-video\"><video controls=\"\" src=\"https:\/\/www.unimedia.tech\/wp-content\/uploads\/2021\/04\/Websocket-Demo.mp4\"><\/video><\/figure>\n\n<p>Espero que este post le resulte \u00fatil.<\/p>\n\n<p><\/p>\n\n<h3 class=\"wp-block-heading\">Unimedia Technology<\/h3>\n\n<p>En <a href=\"https:\/\/www.unimedia.tech\/\">Unimedia Technology<\/a> contamos con un equipo de<strong> Desarrolladores BackEnd<\/strong> que pueden ayudarle a desarrollar sus Aplicaciones m\u00e1s complejas<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hola a todos, As\u00ed que hoy vamos a discutir sobre la implementaci\u00f3n de websocket con serverless mediante el uso de Node.js con Typescript para la comunicaci\u00f3n en tiempo real. Si intenta buscarlo en Internet, encontrar\u00e1 fragmentos de detalles de aplicaci\u00f3n , pero no hay nada concreto. As\u00ed que, aqu\u00ed estoy construyendo un ecosistema websocket completo [&hellip;]<\/p>\n","protected":false},"author":6,"featured_media":6626,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[228,220],"tags":[],"class_list":["post-7399","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-sin-servidor","category-technical-guides-es"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v21.6 (Yoast SEO v27.1.1) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Tiempo real con serverless usando Websocket en AWS - Unimedia Technology<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.unimedia.tech\/es\/tiempo-real-con-serverless-usando-websocket-en-aws\/\" \/>\n<meta property=\"og:locale\" content=\"es_ES\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Tiempo real con serverless usando Websocket en AWS\" \/>\n<meta property=\"og:description\" content=\"Hola a todos, As\u00ed que hoy vamos a discutir sobre la implementaci\u00f3n de websocket con serverless mediante el uso de Node.js con Typescript para la comunicaci\u00f3n en tiempo real. Si intenta buscarlo en Internet, encontrar\u00e1 fragmentos de detalles de aplicaci\u00f3n , pero no hay nada concreto. As\u00ed que, aqu\u00ed estoy construyendo un ecosistema websocket completo [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.unimedia.tech\/es\/tiempo-real-con-serverless-usando-websocket-en-aws\/\" \/>\n<meta property=\"og:site_name\" content=\"Unimedia Technology\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.linkedin.com\/company\/unimedia-technology\/\" \/>\n<meta property=\"article:published_time\" content=\"2021-04-28T13:26:23+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2023-12-21T09:32:18+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.unimedia.tech\/wp-content\/uploads\/2023\/12\/websocket-serverless-ts-4.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1120\" \/>\n\t<meta property=\"og:image:height\" content=\"660\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Unimedia\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@UnimediaCTO\" \/>\n<meta name=\"twitter:site\" content=\"@UnimediaCTO\" \/>\n<meta name=\"twitter:label1\" content=\"Escrito por\" \/>\n\t<meta name=\"twitter:data1\" content=\"Unimedia\" \/>\n\t<meta name=\"twitter:label2\" content=\"Tiempo de lectura\" \/>\n\t<meta name=\"twitter:data2\" content=\"11 minutos\" \/>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Tiempo real con serverless usando Websocket en AWS - Unimedia Technology","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.unimedia.tech\/es\/tiempo-real-con-serverless-usando-websocket-en-aws\/","og_locale":"es_ES","og_type":"article","og_title":"Tiempo real con serverless usando Websocket en AWS","og_description":"Hola a todos, As\u00ed que hoy vamos a discutir sobre la implementaci\u00f3n de websocket con serverless mediante el uso de Node.js con Typescript para la comunicaci\u00f3n en tiempo real. Si intenta buscarlo en Internet, encontrar\u00e1 fragmentos de detalles de aplicaci\u00f3n , pero no hay nada concreto. As\u00ed que, aqu\u00ed estoy construyendo un ecosistema websocket completo [&hellip;]","og_url":"https:\/\/www.unimedia.tech\/es\/tiempo-real-con-serverless-usando-websocket-en-aws\/","og_site_name":"Unimedia Technology","article_publisher":"https:\/\/www.linkedin.com\/company\/unimedia-technology\/","article_published_time":"2021-04-28T13:26:23+00:00","article_modified_time":"2023-12-21T09:32:18+00:00","og_image":[{"width":1120,"height":660,"url":"https:\/\/www.unimedia.tech\/wp-content\/uploads\/2023\/12\/websocket-serverless-ts-4.png","type":"image\/png"}],"author":"Unimedia","twitter_card":"summary_large_image","twitter_creator":"@UnimediaCTO","twitter_site":"@UnimediaCTO","twitter_misc":{"Escrito por":"Unimedia","Tiempo de lectura":"11 minutos"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.unimedia.tech\/es\/tiempo-real-con-serverless-usando-websocket-en-aws\/#article","isPartOf":{"@id":"https:\/\/www.unimedia.tech\/es\/tiempo-real-con-serverless-usando-websocket-en-aws\/"},"author":{"name":"Unimedia","@id":"https:\/\/www.unimedia.tech\/es\/#\/schema\/person\/3a250aa22526d5c9ff6bc95bb380a5dd"},"headline":"Tiempo real con serverless usando Websocket en AWS","datePublished":"2021-04-28T13:26:23+00:00","dateModified":"2023-12-21T09:32:18+00:00","mainEntityOfPage":{"@id":"https:\/\/www.unimedia.tech\/es\/tiempo-real-con-serverless-usando-websocket-en-aws\/"},"wordCount":979,"commentCount":0,"publisher":{"@id":"https:\/\/www.unimedia.tech\/es\/#organization"},"image":{"@id":"https:\/\/www.unimedia.tech\/es\/tiempo-real-con-serverless-usando-websocket-en-aws\/#primaryimage"},"thumbnailUrl":"https:\/\/www.unimedia.tech\/wp-content\/uploads\/2023\/12\/websocket-serverless-ts-4.png","articleSection":["Sin servidor","Technical Guides"],"inLanguage":"es","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.unimedia.tech\/es\/tiempo-real-con-serverless-usando-websocket-en-aws\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.unimedia.tech\/es\/tiempo-real-con-serverless-usando-websocket-en-aws\/","url":"https:\/\/www.unimedia.tech\/es\/tiempo-real-con-serverless-usando-websocket-en-aws\/","name":"Tiempo real con serverless usando Websocket en AWS - Unimedia Technology","isPartOf":{"@id":"https:\/\/www.unimedia.tech\/es\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.unimedia.tech\/es\/tiempo-real-con-serverless-usando-websocket-en-aws\/#primaryimage"},"image":{"@id":"https:\/\/www.unimedia.tech\/es\/tiempo-real-con-serverless-usando-websocket-en-aws\/#primaryimage"},"thumbnailUrl":"https:\/\/www.unimedia.tech\/wp-content\/uploads\/2023\/12\/websocket-serverless-ts-4.png","datePublished":"2021-04-28T13:26:23+00:00","dateModified":"2023-12-21T09:32:18+00:00","breadcrumb":{"@id":"https:\/\/www.unimedia.tech\/es\/tiempo-real-con-serverless-usando-websocket-en-aws\/#breadcrumb"},"inLanguage":"es","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.unimedia.tech\/es\/tiempo-real-con-serverless-usando-websocket-en-aws\/"]}]},{"@type":"ImageObject","inLanguage":"es","@id":"https:\/\/www.unimedia.tech\/es\/tiempo-real-con-serverless-usando-websocket-en-aws\/#primaryimage","url":"https:\/\/www.unimedia.tech\/wp-content\/uploads\/2023\/12\/websocket-serverless-ts-4.png","contentUrl":"https:\/\/www.unimedia.tech\/wp-content\/uploads\/2023\/12\/websocket-serverless-ts-4.png","width":1120,"height":660},{"@type":"BreadcrumbList","@id":"https:\/\/www.unimedia.tech\/es\/tiempo-real-con-serverless-usando-websocket-en-aws\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.unimedia.tech\/es\/"},{"@type":"ListItem","position":2,"name":"Tiempo real con serverless usando Websocket en AWS"}]},{"@type":"WebSite","@id":"https:\/\/www.unimedia.tech\/es\/#website","url":"https:\/\/www.unimedia.tech\/es\/","name":"Unimedia Technology","description":"Your software development partner","publisher":{"@id":"https:\/\/www.unimedia.tech\/es\/#organization"},"alternateName":"Unimedia Tech","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.unimedia.tech\/es\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"es"},{"@type":"Organization","@id":"https:\/\/www.unimedia.tech\/es\/#organization","name":"Unimedia Technology","alternateName":"Unimedia Tech","url":"https:\/\/www.unimedia.tech\/es\/","logo":{"@type":"ImageObject","inLanguage":"es","@id":"https:\/\/www.unimedia.tech\/es\/#\/schema\/logo\/image\/","url":"https:\/\/www.unimedia.tech\/wp-content\/uploads\/2023\/12\/cloud_border-3.png","contentUrl":"https:\/\/www.unimedia.tech\/wp-content\/uploads\/2023\/12\/cloud_border-3.png","width":403,"height":309,"caption":"Unimedia Technology"},"image":{"@id":"https:\/\/www.unimedia.tech\/es\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.linkedin.com\/company\/unimedia-technology\/","https:\/\/x.com\/UnimediaCTO","https:\/\/www.instagram.com\/unimedia.technology\/"]},{"@type":"Person","@id":"https:\/\/www.unimedia.tech\/es\/#\/schema\/person\/3a250aa22526d5c9ff6bc95bb380a5dd","name":"Unimedia","image":{"@type":"ImageObject","inLanguage":"es","@id":"https:\/\/www.unimedia.tech\/es\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/5901fd1c4628e2b48ffd4e47324e8fe0751b39e556a167f078471d4c4bec0f6f?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/5901fd1c4628e2b48ffd4e47324e8fe0751b39e556a167f078471d4c4bec0f6f?s=96&d=mm&r=g","caption":"Unimedia"}}]}},"_links":{"self":[{"href":"https:\/\/www.unimedia.tech\/es\/wp-json\/wp\/v2\/posts\/7399","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.unimedia.tech\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.unimedia.tech\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.unimedia.tech\/es\/wp-json\/wp\/v2\/users\/6"}],"replies":[{"embeddable":true,"href":"https:\/\/www.unimedia.tech\/es\/wp-json\/wp\/v2\/comments?post=7399"}],"version-history":[{"count":0,"href":"https:\/\/www.unimedia.tech\/es\/wp-json\/wp\/v2\/posts\/7399\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.unimedia.tech\/es\/wp-json\/wp\/v2\/media\/6626"}],"wp:attachment":[{"href":"https:\/\/www.unimedia.tech\/es\/wp-json\/wp\/v2\/media?parent=7399"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.unimedia.tech\/es\/wp-json\/wp\/v2\/categories?post=7399"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.unimedia.tech\/es\/wp-json\/wp\/v2\/tags?post=7399"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}