Creando aplicaciones Node.js con TypeScript – Parte 1 #nodejs
TypeScript es la respuesta de Microsoft a la necesidad de crear aplicaciones web robustas y mantenibles utilizando JavaScript. Esto lo consigue mediante:
- Código más homogéneo y mantenible
- Comprobación estática de tipos (opcional y de momento sólo con Visual Studio)
- Object Oriented Programming: clases, herencia e interfaces
- Separation of Concerns (SoC): mediante la organización del código en módulos que se pueden cargar de forma dinámica
En este link: http://programandonet.com/web/primeros-pasos-con-typescript/ tenéis un tutorial sobre las features básicas de TypeScript.
A diferencia de otros lenguajes que compilan JavaScript, como pueden ser Dart o CoffeScript, TypeScript es un superconjunto de JavaScript, por lo que en esencia es JavaScript.
Por tanto, si ya conocemos JavaScript, no tendremos ningún problema para utilizar TypeScript. La curva de aprendizaje de este nuevo lenguaje será realmente suave, pudiendo conseguir grandes resultados desde el principio. Además, al ser JavaScript, TypeScript nos permite seguir utilizando todas las librerías que conocemos hasta ahora (JQuery, Backbone, Knockout, …)
Hay que destacar también que TypeScript es un proyecto Open Source, y se puede descargar su código fuente (además de varios ejemplos) desde aquí: http://typescript.codeplex.com/ Actualmente se encuentra en la versión 0.8.3, y como podéis ver en este roadmap, en la versión final (1.0) estará completamente alineado con la versión 6 de ECMAScript, que traerá de serie alguna de las novedades que ya implementa TypeScript.
Existen varias formas de utilizar este nuevo lenguaje. La primera sería con proyectos creados con Visual Studio 2012 y el plugin de TypeScript. La segunda, que será sobre la que desarrollemos este artículo, es utilizar el paquete de Node.js y alguno de los IDEs que lo soporta (Sublime Text, Vim, Emacs,…). Para este artículo hemos decidido utilizar Sublime Text, ya que es uno de los que se integra mejor con TypeScript, exceptuando Visual Studio. En este link tenéis los pasos para utilizar TypeScript en Sublime Text tanto el syntax highlighter como la forma de crear una System Build que compile TypeScript:
Instalar el paquete de TypeScript para Node.js
1
npm install -g typescript
– Para instalar el syntax highlighter, que te puedes descargar desde este link, sólo hace falta copiar el fichero typescript.tmplanguage en tu directorio de paquetes de Sublime Text.
– Y sólo falta añadir un Build System para que compile los ficheros TypeScript. Para ello, abrimos Sublime Text, y en Tools –> Build System –> New Build System, añadir lo siguiente:
1
2
3
4
5
{
"selector"
:
"source.ts"
,
"cmd"
: [
"tsc.cmd"
,
"$file"
],
"file_regex"
:
"^(.+?) \\((\\d+),(\\d+)\\): (.+)$"
}
Y con esto ya podemos compilar los fichero TypeScript (con extensión .ts) en sus correspondientes JavaScript simplemente presionando Ctrl+B
MANOS A LA OBRA
Vamos a desarrollar una aplicación muy básica: el sistema de puntuaciones de un juego. Que está basado uno de los ejemplos disponibles en el repositorio de TypeScript en Codeplex (http://www.typescriptlang.org/Samples/#ImageBoard), en la que utilizaremos una serie de paquetes Node que harán nuestro trabajo más sencillo:
- ExpressJS para articular la estructura de la aplicación
- Node-MongoDB para conectar con la base de datos
- JadeJS como motor de gestión de vistas
Para este ejemplo es necesario, además de Node.js, tener MongoDB instalado, si no lo tenéis, podéis seguir estos pasos para instalar MondoDB en vuestro equipo.
Crearemos una estructura muy sencilla de directorios que nos permita tener las diferentes “capas” de la aplicación separadas, ya que usaremos una arquitectura MVC, tenéis disponible todo el código de este artículo en este repositorio de Github . A lo largo del artículo veremos las ventajas de tener los ficheros separados por funcionalidades de esta forma:
/node_modules
/public
— /stylesheets
/routes
— index.ts
/views
— index.jade
— newscore.jade
— layout.jade
app.ts
db.ts
express.d.ts
mongodb.ts
node.d.ts
package.json
Lo primero que haremos será crear el fichero de definición de paquetes para poder instalarlos. Este será nuestro package.json:
1
2
3
4
5
6
7
8
9
10
11
{
"name"
:
"application-name"
,
"version"
:
"0.0.1"
,
"private"
:
true
,
"dependencies"
: {
"express"
:
"2.5.8"
,
"ejs"
:
">= 0.5.0"
,
"jade"
:
">= 0.0.1"
,
"mongodb"
:
">= 1.0.0"
}
}
Y los instalamos con la instrucción:
1
npm install
Con lo que veremos que se añaden los paquetes necesarios a nuestra carpeta node_modules.
A continuación vamos a editar el fichero principal de la aplicación, en nuestro caso será app.ts, que la extensión sea .ts indica que se trata de un fichero con código TypeScript. En este fichero es donde usaremos ExpressJS para establecer el enrutamiento que aceptará nuestra aplicación. Para una guía de uso de Express Framework os recomiendo esta de Javier Viola, en este artículo nos centraremos en la implementación con TypeScript.
Antes de nada, debemos añadir las referencias de lo que vamos a utilizar, esto en TypeScript se hace con la siguiente línea:
1
///<reference path='node.d.ts' />
Los ficheros con extensión *.d.ts indican que dicho fichero tendrá la definición de elementos (módulos, interfaces, clases, variables globales y funciones) que necesita TypeScript para importarlos a la hora de compilar. Pero estos ficheros no generan ningún fichero JavaScript, son simplemente definiciones en TypeScript de librerías JavaScript. Por tanto, igual que los ficheros .ts, no es necesario que se desplieguen en los servidores de producción.
Nota: la extensión .d.ts es una convención y puede que nos encontremos ficheros de definiciones sin ella, ya que no es obligatoria. Estos ficheros de definición son mantenidos por la comunidad de desarrolladores ya que comentaba antes, TypeScript es un proyecto Open Source.
Una vez referenciado el fichero de definiciones que nos permite trabajar con el paquete de Node, procedemos a importar los módulos que utilizaremos en nuestra aplicación:
1
2
3
4
5
import http = module(
"http"
)
import url = module(
"url"
)
import routes = module(
"./routes/index"
)
import db = module(
"./db"
)
import express = module(
"express"
)
Estos import serán traducidos a instrucciones del tipo var express = require(‘express’);siempre que sean necesarias, ya que, como comentábamos antes, TypeScript necesita definiciones para saber qué compilar, aunque a veces no tiene un reflejo en el JavaScript resultante.
Creamos una instancia del objeto express y procedemos a configurarlo. Podemos ver como con TypeScript las funciones anónimas pasan a ser () =>{…}, que son expresiones lambda, luego veremos cómo quedan tras compilar:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var
app = express.createServer();
// Configuration
app.configure(() => {
app.set(
'views'
, __dirname +
'/views'
);
app.set(
'view engine'
,
'jade'
);
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.static(__dirname +
'/public'
));
});
app.configure(
'development'
, () => {
app.use(express.errorHandler({ dumpExceptions:
true
, showStack:
true
}));
});
app.configure(
'production'
, () => {
app.use(express.errorHandler());
});
Ahora asignamos las rutas al que aceptará nuestra aplicación. Como dijimos anteriormente será algo muy sencillo y simplemente tendremos la opción de hacer llamadas GET, que devuelve la lista completa de puntuaciones, y un POST para añadir nuevos elementos. Empecemos sólo con el GET:
1
2
3
4
5
6
7
8
// Routes
app.get(
'/'
, routes.index);
app.listen(3000,
function
(){
console.log(
"Demo Express server listening on port %d in %s mode"
, 3000, app.settings.env);
});
export
var
App = app;
Para comprobar que todo está correcto, compilamos el fichero app.ts y obtenemos su correspondiente fichero JavaScript:
Vamos a crear un fichero con el nombre index.ts, que se trataría de nuestro “Controlador” (recordemos que vamos a seguir el patrón MVC) dentro de nuestra carpeta /routes y añadimos el siguiente código:
1
2
3
4
5
6
7
8
9
10
// Importamos los módulos que vamos a utilizar
import express = module(
"express"
)
import db = module(
"../db"
)
// Y exponemos un método que devuelva los elementos de la tabla "highscores".
export
function
index(req: express.ExpressServerRequest, res: express.ExpressServerResponse){
db.getScores(
function
(scores) {
res.render(
'index'
, { title:
'Highscores'
, scores: scores })
});
};
Como vemos, hacemos referencia al módulo “db” y llamamos a un método getScores. Esto lo hacemos así para separar el acceso a datos del sistema de rutas y de la generación de las vistas.
TypeScript es la respuesta de Microsoft a la necesidad de crear aplicaciones web robustas y mantenibles utilizando JavaScript. Esto lo consigue mediante:
- Código más homogéneo y mantenible
- Comprobación estática de tipos (opcional y de momento sólo con Visual Studio)
- Object Oriented Programming: clases, herencia e interfaces
- Separation of Concerns (SoC): mediante la organización del código en módulos que se pueden cargar de forma dinámica
En este link: http://programandonet.com/web/primeros-pasos-con-typescript/ tenéis un tutorial sobre las features básicas de TypeScript.
A diferencia de otros lenguajes que compilan JavaScript, como pueden ser Dart o CoffeScript, TypeScript es un superconjunto de JavaScript, por lo que en esencia es JavaScript.
Por tanto, si ya conocemos JavaScript, no tendremos ningún problema para utilizar TypeScript. La curva de aprendizaje de este nuevo lenguaje será realmente suave, pudiendo conseguir grandes resultados desde el principio. Además, al ser JavaScript, TypeScript nos permite seguir utilizando todas las librerías que conocemos hasta ahora (JQuery, Backbone, Knockout, …)
Hay que destacar también que TypeScript es un proyecto Open Source, y se puede descargar su código fuente (además de varios ejemplos) desde aquí: http://typescript.codeplex.com/ Actualmente se encuentra en la versión 0.8.3, y como podéis ver en este roadmap, en la versión final (1.0) estará completamente alineado con la versión 6 de ECMAScript, que traerá de serie alguna de las novedades que ya implementa TypeScript.
Existen varias formas de utilizar este nuevo lenguaje. La primera sería con proyectos creados con Visual Studio 2012 y el plugin de TypeScript. La segunda, que será sobre la que desarrollemos este artículo, es utilizar el paquete de Node.js y alguno de los IDEs que lo soporta (Sublime Text, Vim, Emacs,…). Para este artículo hemos decidido utilizar Sublime Text, ya que es uno de los que se integra mejor con TypeScript, exceptuando Visual Studio. En este link tenéis los pasos para utilizar TypeScript en Sublime Text tanto el syntax highlighter como la forma de crear una System Build que compile TypeScript:
Instalar el paquete de TypeScript para Node.js
1
| npm install -g typescript |
– Para instalar el syntax highlighter, que te puedes descargar desde este link, sólo hace falta copiar el fichero typescript.tmplanguage en tu directorio de paquetes de Sublime Text.
– Y sólo falta añadir un Build System para que compile los ficheros TypeScript. Para ello, abrimos Sublime Text, y en Tools –> Build System –> New Build System, añadir lo siguiente:
1
2
3
4
5
| { "selector" : "source.ts" , "cmd" : [ "tsc.cmd" , "$file" ], "file_regex" : "^(.+?) \\((\\d+),(\\d+)\\): (.+)$" } |
Y con esto ya podemos compilar los fichero TypeScript (con extensión .ts) en sus correspondientes JavaScript simplemente presionando Ctrl+B
MANOS A LA OBRA
Vamos a desarrollar una aplicación muy básica: el sistema de puntuaciones de un juego. Que está basado uno de los ejemplos disponibles en el repositorio de TypeScript en Codeplex (http://www.typescriptlang.org/Samples/#ImageBoard), en la que utilizaremos una serie de paquetes Node que harán nuestro trabajo más sencillo:
- ExpressJS para articular la estructura de la aplicación
- Node-MongoDB para conectar con la base de datos
- JadeJS como motor de gestión de vistas
Para este ejemplo es necesario, además de Node.js, tener MongoDB instalado, si no lo tenéis, podéis seguir estos pasos para instalar MondoDB en vuestro equipo.
Crearemos una estructura muy sencilla de directorios que nos permita tener las diferentes “capas” de la aplicación separadas, ya que usaremos una arquitectura MVC, tenéis disponible todo el código de este artículo en este repositorio de Github . A lo largo del artículo veremos las ventajas de tener los ficheros separados por funcionalidades de esta forma:
/node_modules
/public
— /stylesheets
/routes
— index.ts
/views
— index.jade
— newscore.jade
— layout.jade
app.ts
db.ts
express.d.ts
mongodb.ts
node.d.ts
package.json
/public
— /stylesheets
/routes
— index.ts
/views
— index.jade
— newscore.jade
— layout.jade
app.ts
db.ts
express.d.ts
mongodb.ts
node.d.ts
package.json
Lo primero que haremos será crear el fichero de definición de paquetes para poder instalarlos. Este será nuestro package.json:
1
2
3
4
5
6
7
8
9
10
11
| { "name" : "application-name" , "version" : "0.0.1" , "private" : true , "dependencies" : { "express" : "2.5.8" , "ejs" : ">= 0.5.0" , "jade" : ">= 0.0.1" , "mongodb" : ">= 1.0.0" } } |
Y los instalamos con la instrucción:
1
| npm install |
Con lo que veremos que se añaden los paquetes necesarios a nuestra carpeta node_modules.
A continuación vamos a editar el fichero principal de la aplicación, en nuestro caso será app.ts, que la extensión sea .ts indica que se trata de un fichero con código TypeScript. En este fichero es donde usaremos ExpressJS para establecer el enrutamiento que aceptará nuestra aplicación. Para una guía de uso de Express Framework os recomiendo esta de Javier Viola, en este artículo nos centraremos en la implementación con TypeScript.
Antes de nada, debemos añadir las referencias de lo que vamos a utilizar, esto en TypeScript se hace con la siguiente línea:
1
| ///<reference path='node.d.ts' /> |
Los ficheros con extensión *.d.ts indican que dicho fichero tendrá la definición de elementos (módulos, interfaces, clases, variables globales y funciones) que necesita TypeScript para importarlos a la hora de compilar. Pero estos ficheros no generan ningún fichero JavaScript, son simplemente definiciones en TypeScript de librerías JavaScript. Por tanto, igual que los ficheros .ts, no es necesario que se desplieguen en los servidores de producción.
Nota: la extensión .d.ts es una convención y puede que nos encontremos ficheros de definiciones sin ella, ya que no es obligatoria. Estos ficheros de definición son mantenidos por la comunidad de desarrolladores ya que comentaba antes, TypeScript es un proyecto Open Source.
Una vez referenciado el fichero de definiciones que nos permite trabajar con el paquete de Node, procedemos a importar los módulos que utilizaremos en nuestra aplicación:
1
2
3
4
5
| import http = module( "http" ) import url = module( "url" ) import routes = module( "./routes/index" ) import db = module( "./db" ) import express = module( "express" ) |
Estos import serán traducidos a instrucciones del tipo var express = require(‘express’);siempre que sean necesarias, ya que, como comentábamos antes, TypeScript necesita definiciones para saber qué compilar, aunque a veces no tiene un reflejo en el JavaScript resultante.
Creamos una instancia del objeto express y procedemos a configurarlo. Podemos ver como con TypeScript las funciones anónimas pasan a ser () =>{…}, que son expresiones lambda, luego veremos cómo quedan tras compilar:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| var app = express.createServer(); // Configuration app.configure(() => { app.set( 'views' , __dirname + '/views' ); app.set( 'view engine' , 'jade' ); app.use(express.bodyParser()); app.use(express.methodOverride()); app.use(express.static(__dirname + '/public' )); }); app.configure( 'development' , () => { app.use(express.errorHandler({ dumpExceptions: true , showStack: true })); }); app.configure( 'production' , () => { app.use(express.errorHandler()); }); |
Ahora asignamos las rutas al que aceptará nuestra aplicación. Como dijimos anteriormente será algo muy sencillo y simplemente tendremos la opción de hacer llamadas GET, que devuelve la lista completa de puntuaciones, y un POST para añadir nuevos elementos. Empecemos sólo con el GET:
1
2
3
4
5
6
7
8
| // Routes app.get( '/' , routes.index); app.listen(3000, function (){ console.log( "Demo Express server listening on port %d in %s mode" , 3000, app.settings.env); }); export var App = app; |
Para comprobar que todo está correcto, compilamos el fichero app.ts y obtenemos su correspondiente fichero JavaScript:
Vamos a crear un fichero con el nombre index.ts, que se trataría de nuestro “Controlador” (recordemos que vamos a seguir el patrón MVC) dentro de nuestra carpeta /routes y añadimos el siguiente código:
1
2
3
4
5
6
7
8
9
10
| // Importamos los módulos que vamos a utilizar import express = module( "express" ) import db = module( "../db" ) // Y exponemos un método que devuelva los elementos de la tabla "highscores". export function index(req: express.ExpressServerRequest, res: express.ExpressServerResponse){ db.getScores( function (scores) { res.render( 'index' , { title: 'Highscores' , scores: scores }) }); }; |
Como vemos, hacemos referencia al módulo “db” y llamamos a un método getScores. Esto lo hacemos así para separar el acceso a datos del sistema de rutas y de la generación de las vistas.
Creando aplicaciones Node.js con TypeScript – Parte 2 #nodejs
Accediendo a la base de datos
Editamos el fichero db.ts y añadimos aquí el código que accede a nuestra base de datos MongoDB. Para ello, importamos el módulo de mongodb y creamos una instancia de la clase mongodb, establecemos la conexión con la base de datos y la abrimos:
1
2
3
4
| import mongodb = module( 'mongodb' ); var server = new mongodb.Server( 'localhost' , 27017, {auto_reconnect: true }, {}) var db = new mongodb.Db( 'highscores' , server); db.open( function () {}); |
Una vez abierta la conexión, definimos una clase que describe los objetos que esperamos enviar/recibir de la colección “highscores”. Esta es una de las ventajas de utilizar TypeScript, que nos permite definir y trabajar con clases, e indicar el tipo de los parámetros de nuestras funciones.
1
2
3
4
5
| export class Score { _id: string; user: string; score : number; } |
Ahora podremos definir las funciones que expone este módulo. Veremos que hay ciertos elementos nuevos con respecto a una definición en JavaScript.
1
2
3
4
5
6
7
8
9
10
11
| export function getScores(callback: (scores: Score[]) => void) { db.collection( 'scores' , function (error, scores_collection) { if (error) { console.error(error); return ; } scores_collection.find({}).toArray( function (error, scoresobj) { if (error) { console.error(error); return ; } callback(scoresobj); }); }); } |
La primera la vemos en los parámetros, estamos indicando que tendremos un callback, que será de tipo void (esto es, que no devuelve nada) la cual recibirá una lista de objetos de tipo Score (la clase que acabamos de definir). Este callback será la función anónima que acabamos de definir en el módulo index:
1
2
3
| (scores) => { res.render( 'index' , { title: 'Highscores' , scores: scores }) } |
El resto de una función es la típica que provee el módulo de mongoDB. Pero ya podemos ver la facilidad con la que podríamos cambiar el servidor al que nos conectamos o incluso el repositorio de datos…
Definiendo las vistas
Gracias a JadeJS podemos cambiar el aspecto de la UI de una forma muy sencilla. En nuestro caso sólo vamos a definir una vista en la que mostrar la lista de puntuaciones. Para ello creamos 2 ficheros: layout.jade para tener unos elementos comunes e index.jade para mostrar la lista:
layout.jade
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| !!! html head title ImageBoard link(rel= 'stylesheet' , href= '/stylesheets/style.css' ) body #header.container_12 #container.container_12!= body index.jade h1= title p Welcome to #{title} ul - each score in scores li b= score.user + " " + score.score |
index.jade
1
2
3
4
5
6
7
| h1= title p Welcome to #{title} ul - each score in scores li b= score.user + " " + score.score |
No es intención de este artículo explicar cómo utilizar Jade, pero vemos que podemos recorrer de una forma muy sencilla una lista de elementos, en nuestro caso la clasificación de puntuaciones, y mostrarlas en una página.
Si desde SublimeText compilamos el fichero app.ts podremos ver cómo se generan todos los ficheros JavaScript que serán los que utilice nuestra aplicación. Gracias a la Build Definition que hemos creado anteriormente, no es necesario hacerlo por cada fichero .ts, sino que se hace automáticamente para todos los que necesitamos. Y ya podemos arrancar la aplicación:
1
| node app.js |
y abrimos un navegador para ver el resultado:
Podemos ver que la página se renderiza correctamente pero no nos muestra ningún resultado, esto es normal puesto que no tenemos ningún registro en nuestra colección MongoDB. Vamos a hacer una página para insertar registros.
Añadiendo más funcionalidades
Siguiendo el mismo proceso que antes, lo primero que hacemos es modificar nuestro fichero de aplicación (app.ts) para añadir dos métodos nuevos, un GET que cargue un formulario y un POST para insertar los datos:
1
2
3
4
5
6
7
| app.get( '/scores/newscore' , (req, res) => { routes.newscoreget(req, res); }); app.post( '/scores/newscore' , (req, res) => { routes.newscorepost(req, res); }); |
Definimos los nuevos métodos en nuestro fichero index.ts:
1
2
3
4
5
6
7
8
9
10
11
12
| export function newscoreget(req: express.ExpressServerRequest, res: express.ExpressServerResponse){ res.render( 'newscore' ,{}); }; export function newscorepost (req: express.ExpressServerRequest, res: express.ExpressServerResponse){ var newscore = new db.Score() newscore.user = req.param( 'user' ); newscore.score = req.param( 'score' ); db.addScore(newscore, (r) => { res.redirect( '/' ); }); }; |
Como vemos, el primero únicamente renderiza el formulario y es el segundo método el que llamará a nuestro módulo de acceso a datos. En este segundo método utilizamos un objeto de la clase Score que hemos definido en otro módulo, que será lo que pasemos como parámetro al método addScore. Veamos cómo implementamos este método:
1
2
3
4
5
6
7
8
9
10
| export function addScore(newscore : Score, callback: (result: any)=> void){ db.collection( 'scores' , function (error, scores_collection) { if (error) { console.error(error); return ; } scores_collection.insert({ "user" : newscore.user, "score" : newscore.score }, (error, result) => { if (error) { console.error(error); return ; } callback(result); }) }); } |
Gracias a que utilizamos una clase de TypeScript, podemos ver sus propiedades cuando las necesitemos:
Y por último, creamos la vista newscore.jade con el formulario en el que añadimos el usuario y la puntuación:
1
2
3
4
5
6
7
8
9
10
11
12
13
| form( method= "post" ) div br div span.inputtitle User name : input(type= "text" , name= "user" ) br div span.inputtitle Score : input(type= "text" , name= "score" ) br #editScoreSubmit input.button(type= "submit" , value= "Save Score" ) |
Y modificamos nuestra vista principal para añadirle un botón que llame a nuestra recién creada vista:
1
2
3
4
5
6
7
8
9
10
11
12
| h1= title p Welcome to #{title} ul - each score in scores li b= score.user + " " + score.score br form( action= "/scores/newscore/" , method= "get" ) div #newScoreSubmit input.button(type= "submit" , value= "New Score" ) |
Volvemos a compilar el fichero app.ts y arrancamos la aplicación, si todo ha ido bien ya podremos acceder al formulario para añadir puntuaciones:
RESUMIENDO
Este pequeño ejemplo nos ha servido para ver cómo se puede hacer una aplicación web con NodeJS y TypeScript con una buena estructura, el código bien organizado y siguiendo el patrón MVC. Esto nos permite que, si la aplicación es muy grande, el trabajo se pueda repartir entre diferentes personas/grupos de manera muy sencilla. Además, podemos tener definidos módulos de prueba (fakes) que simulen funcionalidades que no están desarrolladas.
Autor de éste Artículo
Pablo Bouzada, desarrollador especializado en .NET y otras tecnologías Microsoft. Consultor en Pasiona Consulting y divulgador tecnológico en Techdencias. Colaborador de programandonet.com y otros grupos de usuarios de España.
Twitter: @pbousan
Slideshare: http://www.slideshare.net/pbousan
Oscar perez
0 comentarios:
Publicar un comentario