Azure Functions con TypeScript

No os ha pasado alguna vez que al leer un artículo en lugar de leer los textos explicativos que su autor ha añadido, vais directamente al contenido de los code snipets y vais copiando y pegando… No os ha pasado que ignoráis la prosa que tanto tiempo ha costado escribir y solo leéis el código, porque ya os resulta bastante auto explicativo… Pues hoy es el día en el que he entendido vuestras necesidades. Voy a poner código y reducir mis comentarios a la mínima expresión.

TypeScript con Azure Functions

Cada día que paso trabajando con TypeScript en un contexto de equipo, pienso en lo afortunados que somos de no haber elegido JavaScript. Tener un lenguaje tipado no solo te sirve de red de seguridad, también te ayuda a entender el código que han hecho los demás y poder interactuar con otros artefactos fácilmente gracias al intellisense.

El problema es que cuando empezamos a trabajar con Azure Functions nos encontramos con que podemos desarrollar con los lenguajes C#, Java, JavaScript o Python, pero no con TypeScript. Así que aprovechando que estoy un poco aburrido, he decidido invertir un poco de mi preciado tiempo, montando lo que llamaríamos un boilerplate.

Nuevo proyecto usando la consola.

Para empezar a trabajar, vamos a crear un nuevo proyecto. Para ello crearemos una carpeta nueva usando la consola (en mi caso uso Cmder, que usa el símbolo lambda antes de cada línea).

λ mkdir azure-functions-typescript-boilerplate
λ cd azure-functions-typescript-boilerplate\
Creamos el package.json.

Cuando creamos el archivo package.json usando npm, el comando nos irá preguntando por el nombre del paquete, la versión, datos sobre el autor, repositorio de código y la licencia. Lo rellenamos.

λ npm init
Instalamos las Azure Functions Tools.

Microsoft ha publicado una herramienta de consola para hacer que la creación de Functions sea más sencilla. Para ello tendremos que instalarla como dependencia de desarrollo de nuestro proyecto, o bien como herramienta global, cambiando el parámetro --save-dev por -g.

Personalmente prefiero instalar todas las dependencias de un proyecto de forma local y no global, ya que no todo el mundo tiene por qué tener instalados los mismos paquetes npm, ni tampoco creo que desarrollar un proyecto deba implicar instalar paquetes globales. Pero esto es tan solo una opinión, cada uno es libre de instalar lo que considere de forma global.

λ npm install --save-dev azure-functions-core-tools
Configurando las Azure Functions Tools.

Si has elegido instalar localmente las tools, para poder usarlas tendremos que cambiar el PATH actual añadiendo la carpeta "node_modules\.bin" de nuestro directorio de trabajo. Este cambio solo será efectivo durante la sesión de consola. Es decir, que una vez cerrada la consola actual, ya no existirá este cambio y volveremos al PATH original del sistema.

Por otro lado, si has instalado las tools de forma global, este paso lo puedes ignorar.

λ set PATH=%PATH%;[currrent_folder_fullpath]\node_modules\.bin
Creando el proyecto de Azure Functions en node.

Usando la tool de Microsoft que acabamos de instalar, nos pedirá que seleccionemos el lenguaje de programación que vamos a usar. Ahí elegiremos node.

λ func init
Select a worker runtime:
dotnet
node                             <------------------
python (preview)
Añadiendo paquetes de TypeScript.

Cuando decimos que no hay soporte para TypeScript en Azure Functions, no quiere decir que no tengamos ya los archivos de tipos publicados. Así que, vamos a instalar el lenguaje TypeScript y los tipos necesarios para desarrollar Azure Functions.

λ npm install --save-dev typescript @azure/functions
Añadiendo paquetes de utilidades.

Con el fin de tener unos comandos rápidos para borrar y copiar archivos en nuestros scripts de npm, vamos a añadir los paquetes rimraf y copyfiles. Además del paquete azure-functions-pack, que nos ayuda a crear paquetes de funciones optimizados para funcionar en la plataforma de Azure Functions.

λ npm install --save-dev copyfiles rimraf azure-functions-pack
Ordenando el código.

¡Que no te engañen! Ser ordenado es una virtud, no un defecto. Si cada vez que abro un proyecto de TypeScript veo una carpeta llamada src donde sé a priori que voy a encontrar el código fuente, ¿por qué este proyecto no debería tenerla? ¡Vamos al lío!

λ mkdir src
λ mv host.json local.settings.json src
Abrimos Visual Studio Code.

Ahora necesitamos un editor de código para retocar y crear ciertos archivos de la configuración. Para ello usaremos Visual Studio Code.

λ code .
Creamos el archivo tsconfig.json.

En la raíz de nuestra solución crearemos un nuevo archivo (tsconfig.json) con la configuración necesaria para "transpilar" el código TypeScript en código JavaScript compatible con el runtime de las Azure Functions. Lo más importante es tener en cuenta que usaremos el sistema de módulos de "commonjs" (module.export...) y la versión de ECMAScript de 2017 (con compatibilidad con async y await).

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es2017",
    "lib": ["dom","es2017"],
    "sourceMap": true,
    "allowJs": false,
    "moduleResolution": "node",
    "experimentalDecorators": true,
    "rootDir": "./src",
    "forceConsistentCasingInFileNames": true,
    "suppressImplicitAnyIndexErrors": true,
    "allowSyntheticDefaultImports": true,
    "strictNullChecks":false,
    "noImplicitAny": false,
    "downlevelIteration": true
  },
  "exclude": [
    "node_modules"
  ]
}
Editamos el package.json.

Ahora vamos a añadir una serie de scripts al archivo package.json, que nos van a ayudar en el día a día con nuestro proyecto:

  • new: creará una nueva Function en JavaScript usando las plantillas de la herramienta de Microsoft.
  • serve: transpilará el código y lo ejecutará localmente.
  • build: creará una nueva carpeta llamada "dist" en la que encontraremos los archivos necesarios para su publicación en Azure.
  • clean: borrará los transpilados y la carpeta "dist".

  "scripts": {
    "new": "cd src && func new",
    "serve": "tsc && cd src && func host start && rimraf **/*.js **/*.map",
    "build": "tsc && funcpack pack ./src --copyToOutput && cd src && copyfiles host.json local.settings.json *.csproj ../dist && cd .funcpack && copyfiles **/* ../../dist && cd .. && rimraf .funcpack",
    "clean": "rimraf dist src/**/*.js src/**/*.map"
  },
Creando la primera Function.

Con la ayuda de los scripts de npm que hemos creado, vamos a crear una nueva Function. Para ello tendremos que volver a la consola y ejecutar el comando "npm run new". Durante el proceso elegiremos "HTTP trigger" como desencadenador y por nombre introduciremos "hello_world".

λ npm run new
  
Select a template:
Azure Blob Storage trigger
Azure Cosmos DB trigger
Durable Functions activity
Durable Functions HTTP starter
Durable Functions orchestrator
Azure Event Grid trigger
Azure Event Hub trigger
HTTP trigger                     <------------------
IoT Hub (Event Hub)
Azure Queue Storage trigger
SendGrid
Azure Service Bus Queue trigger
Azure Service Bus Topic trigger
Timer trigger
Select a template: HTTP trigger
Function name: [HttpTrigger] hello_world
Renombrar index.js a index.ts.

Nuestro script ha creado una Function básica en JavaScript. Para convertirla a TypeScript lo primero será cambiar la extensión de ".js" a ".ts", y después tendremos que tipar el código. Si eres un poco vago (como yo) puedes copiar el código expuesto a continuación.

import { Context, HttpRequest } from "@azure/functions";

export default async function (context: Context, req: HttpRequest) {
    context.log('TypeScript HTTP trigger function processed a request.');

    if (req.query.name || (req.body && req.body.name)) {
        context.res = {
            // status: 200, /* Defaults to 200 */
            body: "Hello " + (req.query.name || req.body.name)
        };
    }
    else {
        context.res = {
            status: 400,
            body: "Please pass a name on the query string or in the request body"
        };
    }
};
Probando el proyecto.

Para finalizar tendremos que probar que todo ha salido bien. Para ello lanzaremos en consola el comando "npm run serve" y veremos cómo se lanza un Hosting de Azure Functions y cerca del final de todos los logs nos parecerá la URL de nuestra Function.

λ npm run serve

Http Functions:

        hello_world: [GET,POST] http://localhost:7071/api/hello_world
Abrir la URL en un navegador cualquiera.

Si abrimos la URL en un navegador cualquier y le añadimos un parámetro de QueryString llamado "name", obtendremos un saludo.

http://localhost:7071/api/hello_world?name=Chiquitan%20Chiquititantantan
¡Hala! ¡A cascarla!

Solo queda despedirnos, no sin antes poneros el enlace al proyecto de GitHub donde hemos subido este boilerplate. Sí, ya lo sé... os podíais haber ahorrado todo este artículo. Perdón.

https://github.com/fernandoescolar/azure-functions-typescript-boilerplate