Tweekly5 – Twitter sourced blogs

Met Angular en Firebase Cloud Functions is het niet moeilijk meer om een applicatie te maken die de Twitter API aanspreekt. Maar hoe werkt dit tegenwoordig?

Ik heb het plan om regelmatiger te gaan bloggen, maar hierbij vind ik het lastig om iets te vinden waarover je kunt bloggen.

Twitter kan hiervoor een goed bron zijn, maar hoe krijg ik een goed overzicht van de tweets die interessant waren?

Hiervoor had ik bedacht om een webapp te maken die mijn eigen Twitter likes & favorites uitleest en die per week weergeeft. Zo’n project is een goede kandida09at om weer even met nieuwe technieken te spelen en hiervoor heb ik gekozen voor Cloud functions met Firebase en AngularFire.

En als bijkomend voordeel heb ik gelijk weer iets om een blog van te maken 🙂

In deze blog gaan we stapsgewijs een Angular applicatie maken die via Firebase met Twitter communiceert.
De hoofdstappen zijn als volgt:

  1. Firebase account aanmaken
  2. Angular applicatie maken
  3. Firebase Functions schrijven
  4. Koppelen met twitter

Firebase

Voor Firebase hebben we een account nodig op http://firebase.google.com, waarbij we ook een bestaand Google account kunt koppelen.

Een project starten

We beginnen met het aanmaken van een nieuw project op Inloggen – Google Accounts. Voor dit project kiezen we als naam “tweekly5”. Op het hoofdscherm van de console kunnen we een nieuwe Web applicatie toevoegen. Hier kiezen we de naam “tweekly5-web” en vinken we de optie voor hosting aan. Dit is later nog te wijzigen naar je eigen voorkeur.

De overige stappen zijn voor ons op moment niet nodig, en doorlopen we de rest van de stappen tot continue tot console verschijnt en we op de project pagina terugkomen.

De Tweekly5 app

De webapplicatie maken we in Angular. Vanwege de integratie via AngularFire is het aanspreken van Firebase erg simpel geworden.

We gaan de applicatie in een paar stappen opbouwen:

  1. Angular applicatie die mock data in een tabel toont
  2. Toevoegen AngularFire
  3. Deploy naar Firebase Hosting
  4. Lokaal firebase functions initialiseren
  5. Firebase Function aanroepen met AngularFire
  6. Deploy naar Firebase Functions & Hosting

Via de Angular CLI maken we een nieuwe applicatie aan waarin we gaan ontwikkelen. We kiezen hier voor voor geen routing en scss als styling.

npm install @angular/cli -g
ng new tweekly5-web
cd tweekly5-web

Basis implementatie

We maken gebruik van een tabel om de tweets te tonen, en een mock response dat we later verder gaan vervangen door een call naar Firebase Functions.

export class AppComponent {

  tweets$: Observable;

  ngOnInit() {
    const response = {
      data: [
        { created_at: '123', text: 'foo' },
        { created_at: '123', text: 'foo' },
        { created_at: '123', text: 'foo' },
        { created_at: '123', text: 'foo' },
        { created_at: '123', text: 'foo' },
        { created_at: '123', text: 'foo' },
      ]
  };

  this.tweets$ = of(response).pipe(map(res => res.data));
}

<table>

<tr>
  
<th>Datum</th>

  
<th>Tweet</th>


<tr>


<tr *ngFor="let tweet of tweets$ | async">
  
<td>{{tweet.created_at}}</td>

  
<td>{{tweet.text}}</td>

</tr>

</table>

Start de applicatie met 1 van onderstaande commandos

npm start
# of
ng serve -o

Firebase Hosting

Om AngularFire toe te voegen aan de applicatie maken we gebruik van de ng add functionaliteit van de AngularCLI. De CLI zorgt dan voor de nodige configuratie en het aanmaken van de bestanden.

We voeren het volgende commando uit vanaf de commandline:

ng add @angular/fire

Hiermee worden de volgende packages geinstalleerd:

“@angular/fire”
“firebase”
“@angular-devkit/architect”
“firebase-tools”
“fuzzy”
“inquirer”
“inquirer-autocomplete-prompt”

Na de installatie wordt er gevraagd om een authorization code. Dit opent een browser waarmee we in moeten loggen met het account die we bij Firebase hebben aangemaakt.
Kopieer en plak de code, die in de browser staat, in de terminal, en dan zien we gelijk een lijst met de projecten in Firebase. We kiezen voor ons net aangemaakte project.
Dit maakt de initiele configuratie bestanden voor je aan:

.firebaserc
firebase.json

Deze bestanden bevatten de informatie wat nodig is om de applicatie te kunnen deployen bij Firebase.

Het project is nu geconfigureerd om gedeployed te worden. In angular.json is een nieuw commando erbij gekomen: “deploy”.
Om deze aan te roepen, voegen we in de scripts in package.json een nieuwe commando toe:

"deploy:web": "ng run tweekly5-web:deploy"

Dit commando bouwt het project en deployed het resultaat naar Firebase Hosting.

Voer het commando npm run deploy:web uit en ga naar de URL van de applicatie die in de terminal staat om hem op Firebase te zien draaien! 🙂

Firebase Functions

Via de CLI van firebase kunnen we functions automatisch geconfigureerd toevoegen aan het project:

firebase init functions

Bij de opties kiezen we voor JavaScript, maar TypeScript zou een goede keuze zijn. JavaScript voldoet voor nu zodat we in een latere stap direct code van een andere bron kunnen gebruiken.

function/index.js

In dit bestand plaatsen we het response dat we op dit moment nog in de app.component hebben staan.

const functions = require('firebase-functions');
const cors = require('cors')({origin: true});

exports.tweets = functions.https.onRequest((request, response) => {
  const res = { data: [
    { created_at: '456', text: 'bar' },
    { created_at: '456', text: 'bar' },
    { created_at: '456', text: 'bar' },
    { created_at: '456', text: 'bar' },
    { created_at: '456', text: 'bar' },
    { created_at: '456', text: 'bar' },
  ] };

  /**
  * CORS is hier nodig omdat de functions op een ander adres
  * draaien dan de frontend.
  * https://firebase.google.com/docs/functions/http-events#using_existing_express_apps
  */
  return cors(request, response, () => {
    response.send(res)
  })
});

We gaan nu eerst de tweekly5-web applicatie gereed maken om met firebase functions te communiceren.
Gelukkig maak AngularFire dit gemakkelijk voor ons.

Aanroepen AngularFire functions

Om de webapplicatie te koppelen met onze firebase functions zijn een paar stappen nodig:

  1. environment configuratie voor development & productie
  2. AngularFire modules importeren
  3. AngularFireFunctions aanroepen
  4. Configuratie

Met onderstaande configuratie maken we het mogelijk om lokaal te ontwikkelen met een lokale server en via een production build aan te sluiten op de functions die bij ons firebase project draaien.

environments/environment.prod.ts

// projectId is de waarde die in .firebaserc staat bij targets
// de url zetten we expliciet op leeg, deze wordt door AngularFire uitgezocht.
export const environment = {
  production: true,
  firebase: {
    projectId: 'tweekly5-417c7',
    url: ''
  }
};

environments/environment.ts

// hier zetten we de url op de default waarde die door firebase wordt gebruikt.
export const environment = {
  production: false,
  firebase: {
    projectId: 'tweekly5-417c7',
    url: 'http://localhost:5001'
  }
};

Modules toevoegen

Zoals met alles in Angular, moeten we modules importeren die de functionaliteit toevoegen ana de applicatie.
AngularFireModule zorgt voor de initialisatie van de configuratie, en AngularFireFunctionsModule zorgt voor de service waarmee we de functions kunnen aanroepen.

app.module

imports: [
  ...,
  AngularFireModule.initializeApp(environment.firebase),
  AngularFireFunctionsModule
]

Met onderstaande code configureren we de AngularFire voor lokaal ontwikkelen en productie deployments.

providers: [
  { provide: FUNCTIONS_ORIGIN, useValue: environment.firebase.url }
]
Aanroepen functions

We beginnen met het injecteren van fns: AngularFireFunctions in de constructor van AppComponent.
Wanneer we dan de code betrekkende de response variabele vervangen voor onderstaande, doen we een aanroep naar onze functions.

app.component.ts

ngOnInit() {
  // De parameter van httpsCallable is de naam van de functie die je wilt aanroepen
  const getTweets = this.fns.httpsCallable('tweets/');
  this.tweets$ = getTweets({ name: 'data' });
}

function/index.js

// 'tweets' is nu de url waarop de functie beschikbaar is.
exports.tweets = functions.https.onRequest(app);

De functie die uit httpsCallable terug komt, verwacht een object waarin aangegeven wordt welke property uit het response de data bevat.
In ons geval is dit data:

function/index.js

Promise.all([
  client.get(`favorites/list`),
  client.get(`statuses/user_timeline`, params)
])
.then(response => [...response[0].data, ...response[1].data])
// 'data' is de property die het response bevat
.then(res => (data: res}))
.then(timeline => {
  cache = timeline;
  res.send(timeline);
})
.catch(error => res.send(error));

Start lokaal

We kunnen nu ng serve stoppen. We gaan de applictie starten zoals deze uiteindelijk gedeployed gaat worden.
Hiervoor maken we een nieuwe build van de applicatie en start en we firebase lokaal met onderstaand commando:

firebase serve --only hosting,functions

Wanneer je nu in de browser naar localhost:5000 gaat, worden de “tweets” opgehaald via de Firebase Cloud Function.

Deploy naar Firebase Functions

We voegen eerst een paar scripts toe aan package.json om eerst de functions te deployen voor tweekly5-web wordt gedeployed.

"deploy": "npm run deploy:web",
"predeploy": "npm run deploy:functions",
"deploy:functions": "firebase deploy --only functions",
"deploy:web": "ng run tweekly5-web:deploy"

Nu kunnen we de applicatie deployen met het commando:

npm run deploy

Twitter

Om de applicatie met twitter te laten communiceren, zijn er een aantal stappen nodig

  1. Firebase Pricing Plan
  2. Twitter App aanmaken
  3. Tweekly5 aansluiten op twitter

Firebase Pricing Plan

Omdat we gebruik willen maken van een API van buiten Google via Cloud Functions, voldoet het gratis “Spark” plan niet. Persoonlijk heb ik gekozen voor het Blaze plan, waarbij je externe API’s mag aanroepen, en je betaalt alleen voor de data die je gebruikt. Voor het Blaze plan moet je Billing informatie toevoegen aan je Google Account, waarbij je alleen een credit card kunt toevoegen.

Hierbij heb ik gelijk budgetten toegevoegd zodat ik op de hoogte word gesteld als het verkeer meer is dan verwacht: The Firebase Blog: Adding free usage to Blaze pricing plan.
Om de budgetten in te stellen, klik je op settings naast “Project overview”, en kies je voor “Billing” in het menu. In het nieuwe scherm kan je onder “Budgets & alerts” een budget toevoegen met de gewenste limieten die je acceptabel vind.

Voor persoonlijke projecten zal je niet snel de betaalgrens overgaan, maar mocht je dit zeker willen weten kan je via de calculator kijken wat de kosten zouden kunnen worden.
Wanneer je het project opent in Firebase Console, kan je linksonderin het menu het plan aanpassen voor dit project.

Twitter App

Voor Twitter maken we een ‘Twitter App’ aan. Via deze app krijgen we de benodigde API keys om Twitter te kunnen bevragen.
Volg hiervoor de stappen op https://developer.twitter.com/en/docs/basics/getting-started

Developer Apps

Voor website url & callback url gebruiken we de url van de firebase applicatie. Met de API key van de Twitter app gaan we kunnen we vanuit Firebase Cloud Function de tweets op te halen.

Aansluiten op Twitter

Om de dummy data te vervangen met de aanroep naar Twitter, kunnen we de kennis van het internet gebruiken. Met deze blog: Building a Twitter Client with NodeJS and Angular — SitePoint hebben we een goede basis om te beginnen.

In het kort zijn de volgende stappen hiervoor nodig:

  1. installeer packages
  2. gebruik de library voor de aanroep

Packages

Installeer in de functions folder de volgende dependencies:
functions/package.json

"twit": "^2.2.11",
"express": "^4.16.3",
"cors": "^2.8.4",
"body-parser": "^1.18.3",

en verander in <strong>functions/index.js</strong>

const functions = require('firebase-functions');
const express = require('express');
const Twitter = require('twit');

const app = express();
// Onderstaande gegevens haal je uit de Twitter App configuratie
const client = new Twitter({
  consumer_key: '',
  consumer_secret: '',
  access_token: '',
  access_token_secret: ''
});

app.use(require('cors')());
app.use(require('body-parser').json());

let cache = [];
let cacheAge = 0;

const getTweets = (req, res) =&gt; {
  // Cache van een uur om het aantal calls naar Twitter te limiteren
  if (Date.now() - cacheAge &gt; 60000) {
  cacheAge = Date.now();
    const params = {tweet_mode: 'extended', count: 50};
  if (req.query.since) {
    params.since_id = req.query.since;
  }
  Promise.all([
    client.get(`favorites/list`),
    client.get(`statuses/user_timeline`, params)
  ])
  .then(response =&gt; [...response[0].data, ...response[1].data])
  .then(res =&gt; ({data: res}))
  .then(timeline =&gt; {
    cache = timeline;
    res.send(timeline);
  })
  .catch(error =&gt; res.send(error));
  } else {
    res.send(cache);
  }
};

app.get('/', getTweets); // Deze is makkelijker voor debuggen.
app.post('/', getTweets); // AngularFire doet een POST om de data op te halen

exports.tweets = functions.https.onRequest(app);

Lokaal draaien

Wanneer we het commando weer uitvoeren om de applicatie lokaal te draaien, zien we dat nu onze likes & favorites van het internet worden gehaald.

Laatste deploy

Voer een laatste deploy uit van Firebase Functions om op de je applicatie url je tweets te zien.