¿Qué vamos a construir?
Vamos a construir a una app para buscar la predicción meteorológica en cualquier ciudad del mundo, usando la API de AccuWeather. Aquí puedes ver la versión final. ?
Requisitos y consideraciones previas
Para poder seguir este proyecto sin perderte debes tener conocimientos de:
- Manipulación del DOM
- JavaScript asíncrono
- En general, todos los conceptos básicos de JavaScript
- HTML5
- CSS3 básico, porque vamos a apoyarnos mucho en bootstrap
- Bootstrap 4
Estructura inicial de archivos
Vamos a trabajar con un archivo HTML, un archivo CSS y varios archivos JS (JavaScript). El objetivo de tener varios archivos JS es tener nuestro código más organizado y dividido. Así, nuestro proyecto tendrá la siguiente estructura:
- ?JS
- ?app.js ➡ gestionará lo relacionado con DOM manipulation
- ?forecast.js ➡ gestionará el código responsable de interactuar con la API de predicción meteorológica
- ?index.html
- ?styles.css
1. Creamos el boilerplate de nuestro index.html. Añadimos el CDN de bootstrap y a continuación, un link a nuestro propio archivo CSS. Vinculamos el archivo con nuestros archivos JS, primero forecast.js y segundo, app.js. El orden de los archivos JS importa, ya que el código que escribiremos en app.js dependerá del código escrito en forecast.js, por eso es fundamental que éste vaya primero.
2. Creamos el contenido de nuestra app en el index.html. Vamos a utilizar bootstrap para casi todas las clases que añadamos, sólo vamos a crear un par de clases de CSS propias.
La template de nuestra app va a consistir básicamente en una especie de tarjeta que muestra el tiempo de una ciudad X, con una imagen, un icono y una descripción. También contendrá un pequeño formulario para que el usuario busque la predicción meteorológica de una ciudad.
La imagen será diferente dependiendo de si es de día o de noche para reflejar esa circunstancia. Por el momento vamos a usar un placeholder, pero después inyectaremos las imágenes dinámicamente con JS. Para el placeholder usaremos la web placeholder.com.
3. En el archivo styles.css hacemos una pequeña configuración para mejorar el aspecto de nuestra app.
body { background: #efecd8; letter-spacing: 0.2em; } .container { max-width: 400px; }
Este es su aspecto actual:
Primeros pasos con la API de AccuWeather
Vamos a sentar las bases para interactuar con esta API, extraer los datos que necesitamos y finalmente mostrarlos en nuestra web. Las webs que ofrecen APIs normalmente tienen una URL dedicada a este fin. Este es el caso para AccuWeather (URL para developers).
1. Debemos crear una cuenta gratuita para poder usar sus APIs. La opción "register" está en la navbar superior. Te mandarán un email para que entres en su web y crees una contraseña.
2. La mayoría de APIs nos solicitan que creemos una especie de app a través de la cual vamos a utilizar esa API. Es como tener una estantería e ir añadiendo libros. ? Cada libro sería una "app" de AccuWeather destinado a un fin. Así, al crear una app, AccuWeather le asocia un código (una API key) y nos lo proporciona.
Para crear una app, vamos a "my apps" y le damos a "add a new app". Contestamos a las preguntas que nos hacen: weatherApp, desktop website, worldwide, JavaScript, etc. A continuación nos saldrá que nos han aprobado dicha app. Si entramos en ella, podremos ver la API key que nos han creado.
La API key estará englobada en nuestra http request, de manera que cuando hagamos esa request, estaremos enviando también nuestra API key. Así, AccuWeather sabe qué usuario está haciendo la request a efectos de controlar cuántas requests hace un usuario. ?️♂️
Como estamos en una versión gratuita, existen restricciones sobre el número de requests que podemos hacer al día. Además, sólo podremos crear una app por cuenta. Si por cualquier motivo nos pasamos de las requests diarias, sólo tenemos que darle a "Delete Meteo App" y crear una app nueva. Nos dará una nueva API key que tendremos que sustituir por la que ya habíamos escrito en nuestro código. ? Pero ea, es un mal menor.
3. Copiamos la API key y la pegamos en el archivo forecast.js, dentro de una variable a la que llamamos key.
const key = 'aquí tu API key';
Actualmente tenemos una restricción de 50 requests diarias. Así que cuidado si tienes la función de auto-guardado en tu IDE. ?
En cuanto a la información que vamos a solicitar de esta API, debemos solicitarla en dos partes.
- 1ª: request a un endpoint para obtener información sobre una ciudad X (el código de la ciudad).
- 2ª: request a un endpoint distinto utilizando el código de la ciudad (city code) para obtener las condiciones meteorológicas asociadas a esa ciudad.
Los endpoints los podemos encontrar en la pestaña "API REFERENCE", como es lo habitual en una API. Ahí dentro encontramos los endpoints clasificados por categorías. Nosotros necesitamos la categoría de "locations API" para obtener la información de la ciudad en primer lugar.
4. Hacemos click en "locations API" y localizamos la opción "city search". Ahí está el primer endpoint que vamos a usar. Recordemos que ese endpoint nos devolverá un city code.
5. Volvemos a las categorías y seleccionamos "current conditions API". Ahí vamos a utilizar el endpoint de "current conditions" para hacer nuestra segunda request.
http://dataservice.accuweather.com/currentconditions/v1/{locationKey}
Ese locationKey es en realidad el city code que habremos obtenido previamente. Habiendo hecho esta introducción, vamos a desarrollar los puntos 4 y 5 en profundidad. ?
1ª request: Llamando a la city search API
Sabemos que la primera request que tenemos que hacer es a la locations API para obtener información sobre una ciudad concreta, por ejemplo Zurich. Cuando hagamos esa request, el navegador nos devolverá un conjunto de información entre la que está un location key code (a lo que antes nos hemos referido como city code).
Ese location key code es un identificador único de cada ciudad.
1. El endpoint que queremos es el que nos da la opción de "city search". Si pinchamos ahí, podemos hacer una simulación de una request. Verás que en ningún sitio pone "endpoint". ? Para que no nos hagamos un lío, ten en cuenta esto:
base + queries = endpoint
La base es lo que también se conoce como resource URL. Se llama "base" porque es la parte inicial de un endpoint. Para construir un verdadero endpoint, debemos adjuntarle queries. Esto es, parámetros de nuestra búsqueda.
Por ejemplo, para este caso vamos a necesitar dos parámetros:
- apikey
- q ➡ la ciudad sobre la que queramos información
2. Hacemos el test introduciendo dichos parámetros y dándole al botón "Send this request".
Eso nos debería devolver una serie de datos como esos de la imagen. A nosotros lo que nos interesa es la información de la propiedad Key, porque ese es el identificador de la ciudad que necesitaremos para hacer la segunda request.
?♀️Pasemos a la acción y hagamos esta request desde nuestro código, desde el forecast.js.
3. Creamos una función asíncrona que se encargará de hacer la request. La llamamos getCity. Para que sea asíncrona, le añadimos la keyword async. Creamos una variable dentro de la función llamada base, que contendrá la resource URL de la que hablábamos antes.
En cuanto a los parámetros, sabemos que necesitamos dos (apikey y q). La apikey ya la tenemos, así que nos creamos una variable llamada query que almacenará ambos parámetros. Un parámetro de una query se construye siempre empezando con un signo de interrogación "?".
Sintaxis de una query:
const query = `?nombrePrimerParam=valorPrimerParam&nombreSegundoParam=valorSegundoParam`;
Recuerda que el nombre exacto de los parámetros nos los da AccuWeather. ?♀️ Ojito con esto, porque el nombre debe ser exacto, así que cuidado con las mayúsculas/minúsculas.
Creamos el contenido de la variable query entre template strings, porque así es más sencillo incluir nuestra variable key.
4. El segundo parámetro (q) corresponde a la ciudad. No vamos a escribir el nombre de la ciudad en nuestro código porque eso haría que fuese estático y haríamos siempre la request sobre la misma ciudad. En lugar de eso, le pasamos un argumento a la función getCity. Lo llamamos city.
Así, cuando invoquemos a la función getCity, le pasaremos la ciudad como parámetro.
5. Ya tenemos nuestra base y nuestra query. Ahora tenemos que fusionarlos para crear un endpoint. Para eso, creamos una variable a la que llamamos response y utilizamos la fetch API para crear una promesa y la keyword await para esperar a que la promesa se resuelva. A la fetch API le pasamos la suma de base + query, así JS podrá fusionarlos en uno solo string. ?
6. Convertimos la respuesta que obtendremos en JSON data, usando el método json(). Almacenamos el resultado en una variable llamada data, que también devolverá una promesa.
Hacemos un console.log de la data para que ver qué nos devuelve el navegador. ?
7. Invocamos a la función getCity, pasándole la ciudad que queramos.
const getCity = async (city) => { const base = 'http://dataservice.accuweather.com/locations/v1/cities/search'; const query = `?apikey=${key}&q=${city}`; const response = await fetch(base + query); const data = await response.json(); console.log(data); }; getCity('Zurich');
¡Y voilà! ? En tu consola debería aparecer un array con varios objetos. Todos se refieren a Zurich, pero están en orden de más a menos exactitud. Si desplegamos el primero, veremos que se refiere a Zurich, Suiza. Ese es el que necesitamos.
8. Así que lo seleccionamos en nuestro código. Y en lugar de hacer un console.log, hacemos un return de data.
9. Dado que la función getCity devuelve una promesa, especificamos qué hacer cuando la promesa se resuelva (usando el método then()), o sea rechazada (usando el método catch()).
const getCity = async (city) => { const base = 'http://dataservice.accuweather.com/locations/v1/cities/search'; const query = `?apikey=${key}&q=${city}`; const response = await fetch(base + query); const data = await response.json(); return data[0] }; getCity('Zurich') .then(data => console.log(data)) .catch(err => console.log(err));
Ahora en nuestra consola sólo debería aparecer la información de Zurich, Suiza, incluyendo la propiedad Key.
2ª request: Llamando a la current conditions API
1. Desde nuestra cuenta de AccuWeather, entramos a la sección Current Conditions API y localizamos la opción "Current Conditions" (debería ser la primera). Si hacemos click, entramos en una interface muy similar al proceso de la primera request. Se nos dan ciertas instrucciones y la base URL, igual que antes.
Fíjate que la base URL contiene ya el city code (el código único de cada ciudad).
http://dataservice.accuweather.com/currentconditions/v1/{locationKey}
Así que, como verás en la interface, locationKey no forma parte de los Query Parameters.
? Las llaves son solo para indicarnos que estamos ante una variable, pero no debemos usarlas cuando hagamos nuestra request. Como ves en los Query Parameters, el único query que necesitamos es apikey. Así que volvemos a nuestro forecast.js y hacemos la request desde ahí.
2. Creamos una segunda función antes de getCity, a la que llamamos getWeather, que será la responsable de gestionar esta segunda request para obtener las condiciones meteorológicas de una ciudad X. Esta función es asíncrona, igual que getCity.
// get weather info const getWeather = async () => { };
A esa función debemos pasarle por parámetro la propiedad Key, que es el city code, recuerda. A ese valor tenemos acceso desde el método then(), a través del parámetro data.
Al parámetro que le pasamos a la función getWeather lo llamamos id, para no hacernos un lío entre tanto key, apikey, Key, etc. ?
3. Creamos una variable dentro de la función a la que llamamos base (como en la primera request). Esta variable almacena la resource URL que nos da AccuWeather. Hemos visto que esa URL no está completa hasta que le pasemos una locationKey, y eso podemos hacerlo añadiendo el parámetro id al final.
Para eso tendremos que definir nuestra variable con template strings.
const getWeather = async (id) => { const base = `http://dataservice.accuweather.com/currentconditions/v1/${id}`; };
4. Creamos una variable llamada query que almacene los parámetros de la request. En este caso, como se ve en los requisitos, sólo necesitamos el parámetro apikey. Como tenemos que pasarle nuestra variable key, lo hacemos usando template strings.
5. Utilizamos la fetch API para combinar las variables base + query, creando así un verdadero API endpoint. Utilizamos await para esperar mientras se resuelve la promesa, y guardamos todo en una variable llamada response (mismo proceso que para la primera request).
6. Pasamos la response a formato JSON y la guardamos en una variable llamada data. Hacemos un console.log de data para ver si todo ha salido como esperamos.
const getWeather = async (id) => { const base = `http://dataservice.accuweather.com/currentconditions/v1/${id}`; const query = `?apikey=${key}`; const response = await fetch(base + query); const data = await response.json(); console.log(data); };
7. Llamamos a la función getWeather. Para que la función getCity no se interponga en nuestro camino, la comentamos temporalmente. La función getWeather espera un city code, así que de momento le pasamos el que nos ha devuelto la primera request.
// getCity('Zurich') // .then(data => console.log(data)) // .catch(err => console.log(err)); getWeather("316622");
¡Y ahí lo tenemos! ✌ Ahora en nuestra consola podemos ver un array con únicamente un elemento (un objeto que contiene la información meteorológica de Zurich). Pero tenemos que pulir este sistema, porque en la práctica nadie busca una ciudad por su código único, sino por su nombre (Zurich, Barcelona, etc). ?
8. Así que borramos la llamada a la función getWeather, descomentamos la función getCity y hacemos un return de data dentro de la función getWeather en lugar de un console.log. En lugar de hacer un return de todo el array, lo hacemos del primer (y único) elemento, porque así nos será más fácil trabajar con él.
return data[0];
9. Cuando invocamos a la función getCity, localizamos el punto donde obtenemos la información de dicha ciudad.
Ahí es donde debemos invocar a la función getWeather. ?♂️
10. En lugar de hacer un console.log de data, obtenemos la propiedad Key contenida en ese parámetro y se la pasamos a la función getWeather. Recuerda que debemos hacer un return de la función.
getCity('Zurich') .then(data => { return getWeather(data.Key); }) .catch(err => console.log(err));
11. Ya que eso devuelve otra promesa, podemos vincularle el método then() para gestionar qué sucede cuando la promesa se resuelve. Por ahora hacemos sólo un console.log de los datos provenientes de la función getWeather.
getCity('Zurich') .then(data => { return getWeather(data.Key); }).then(data => { console.log(data); }) .catch(err => console.log(err));
¡Y ya lo tenemos! ? En nuestra consola podemos ver ahora las condiciones meteorológicas de Zurich.
Es momento de trabajar en la funcionalidad de nuestra app, para que un usuario pueda escribir el nombre de una ciudad y recibir la predicción meteorológica correspondiente. Pero eso será en el próximo capítulo de esta serie. ?
THE END!
¡Y con esto terminamos la parte #1 de este proyecto! Espero que hayas aprendido algo nuevo ?. Si te queda alguna duda, ¡nos vemos en los comentarios!
Si quieres seguir aprendiendo, puedes ver la parte #2 aquí.
Y si crees que este post puede serle útil a alguien, ¡compártelo!
Otros artículos que pueden interesarte