Commit 470d298f authored by Robert Hostlowsky's avatar Robert Hostlowsky

split grundlagen und server

parent 295cfa95
......@@ -3,10 +3,11 @@
This contains the parts of my softwerker article (in German)
* [Intro](./intro.md)
* [Grundlagen und Server](grundlagen-und-server.md)
* [Grundlagen](./grundlagen.md)
* [Server](./server.md)
* [Client](./client.md)
* [Cloud-based Service:graph.cool](BaaS.md)
* [Cloud-based Service:graph.cool](./BaaS.md)
* [summary](./summary.md)
This extends my blog post [GraphQL with spotify. Teil 1: server](https://blog.codecentric.de/2017/09/graphql-mit-spotify-teil-1-server/)
by a UI client ...
\ No newline at end of file
by a UI client ...
This diff is collapsed.
Es soll gezeigt werden,
* wie ein einfacher Server mit GraphQL-Schnittstelle aufgesetzt werden kann,
* welche weitere Schritte nötig sind, um die Daten von der Spotify API zu laden und
sie dann per GraphQL zu Verfügung zu stellen.
<img src="https://blog.codecentric.de/files/2016/09/simpspot.png" width="300" />
[GraphQL basierender Spotify client - screenshot](https://blog.codecentric.de/files/2016/09/simpspot
Einfacher Spotify Web Client, der mit unserem GraphQL Server arbeitet
In diesem Artikel wird beschrieben, wie wir beispielhaft einen **einfachen Server** auf Basis der Referenz Implementierungen für Node.js, [graphql-js](https://github.com/graphql/graphql-js) und [express-graphql](https://github.com/graphql/express-graphql), für Spotify Musik Daten aufbauen können.
## Schema für unsere Spotify-Daten
Wir werden im nächsten Schritt ein Schema für unser Beispiel für Spotify-Daten entwerfen und nutzen.
> "Die grundlegenden Komponenten eines GraphQL-Schemas sind Objekttypen, die
> eine bestimmte Art von Objekt mit seinen Feldern darstellen, wie sie von
> einem [REST] Server abgefragt werden können"
Aus [Schemas und Typen](http://graphql.org/learn/schema/)
```javascript
// data/schema.js
import { buildSchema } from 'graphql';
const schema = buildSchema(`
#
# Einfacher Start:
# Wir verwenden hier nur einen Teil der Informationen, die Spotify API zu Verfügung stellt
# z.B. von https://api.spotify.com/v1/artists/3t5xRXzsuZmMDkQzgOX35S
# Bei Bedarf kann dieses Schema also um weitere Felder erweitert werden
#
type Artist {
name: String
image_url: String
albums: [Album]
}
# Album kann auch eine Single repräsentieren...
type Album {
name: String
image_url: String
tracks: [Track]
}
type Track {
name: String
preview_url: String
artists: [Artists]
track_number: Int
}
type Query {
# Liste alle Künstler, deren Namen "byName" enthält
queryArtists(byName: String = "Red Hot Chili Peppers"): [Artist]
}
`);
export default schema;
```
Hier hilft uns das
[GraphQL schema cheatsheet](https://github.com/sogko/graphql-schema-language-cheat-sheet) von Hafiz Ismail großartig.
Wir definierten hier konkrete **Typen**, und auch ihre **Beziehungen**, um die Struktur unseres
Entitäten-Graphen aufzubauen:
1. Uni-direktionale Verbindungen zwischen Entitäten:
* Zum Beispiel **Alben eines Künstlers** (1-m):
Um diese Relation zu definieren, fügen wir einfach ein **Feld** `album` vom **Typ `array` bestehend aus `Album`** zum Typ `Artist` hinzu.
* Jeder einzelne Künstler `Artist` selbst kann wiederum dadurch gefunden
werden, dass die `queryArtists()` Methode mit einem Abfrageparameter `byName` aufgerufen
wird - Rückgabewert ist eine Liste von `Artist`s
* Auf gleiche Weise kann von der Wurzel (oder "Top-Abfrage") aus jeder Knoten in unserem vollständigen Entity-Graphen
erreicht werden: `Query->Artist->Album ->Track`
  Z.B. Lassen sich so alle Titel der Alben "eines bestimmten" Künstlers durch diese GraphQL-Abfrage ermitteln:
```
{
queryArtists(byName:"Red Hot Chili Peppers") {
albums {
name
tracks {
name
}
}
}
}
```
### Schnellstart mit Mocking Server
Mit den vielen Informationen, die bereits im Schema stecken, kann man es direkt verwenden, um Beispieldaten für
den Betrieb mit einem Mock-Servers zu modellieren.
So kann man alle "Strings" oder Zahlen mit Zufallswerten befüllen, und erhält grundsätzlich Daten mit
den gleichen Strukturen...
Um dies zu demonstrieren, verwenden wir den `mockServer` aus der `graphql-tools` Bibliothek und erzeugen
dynamische Dummy-Daten:
```javascript
// test/schemaTest.js
import { mockServer } from 'graphql-tools';
let counter = 0;
const simpleMockServer = mockServer(schema, {
String: () => 'loremipsum ' + (counter ++),
Album: () => {
return {
name: () => { return 'Album One' }
};
}
});
const result = simpleMockServer.query(`{
queryArtists(artistNameQuery:"Marilyn Manson") {
name
albums {
name
tracks {
name
artists { name }
}
}
}
}`).then(result => console.log(JSON.stringify(result, ' ', 1)));
```
In dem Beispielprojekt kann es mit folgendem Aufruf ausprobiert werden
```bash
npm run simpletest
```
Ergebnis:
```json
{
"data": {
"queryArtists": {
"name": "loremipsum 1",
"albums": [
{
"name": "Album One 2",
"tracks": [
{
"name": "loremipsum 3",
"artists": [
{
"name": "loremipsum 4"
},
{
"name": "loremipsum 5"
},
"..." ] } ] } ] } } }
```
Wie man es flexibel anpassen kann, lasst sich hier nachlesen: [apollo's graphql-tools mocking](http://dev.apollodata.com/tools/graphql-tools/mocking.html)
Natürlich könnte man anspruchsvollere Mock-Datenobjekte modellieren und nutzen,
aber es reicht völlig, um es für eine erste client-seitige Entwicklung mit unserem Schema zu benutzen.
## Abfragen/Laden und Senden von echten Musikinfo-Daten
Um "echte" Daten zu erhalten, müssen wir die Resolver-Funktionen - so ähnlich wie die 'hi'-Funktion oben - implementieren.
### Abfrage für Künstler mit bestimmten Namen
In unserem Fall **beginnen** wir mit der Abfrage für einen Künstler mit bestimmten Namen:
_Es kann eine leere Liste oder sogar mehrere Treffer geben._
Zur Erinnerung: Jedes 'Feld' in `rootValue` entspricht einer 'Top-query' mit demselben Namen. Bisher hatten wir nur `hi`.
Wir fügen folgende `queryArtists`-resolver-Implementierung hinzu:
```javascript
// ... in server.js :
app.use('/graphql', expressGraphQL(req => ({
schema,
rootValue: {
queryArtists: (queryArgs) => fetchArtistsByName(queryArgs.byName)
}
// ...
})));
```
Hinweis: Alternativ hätten wir diese Logik auch direkt in die Schema Definition 1 hinzufügen können.
Dadurch würde die Logik für die Datenabfrage mit der Typdefinitionen gemischt wird.
Ich bevorzuge deshalb die zweite Variante der Schema-Definition, denn dabei lässt sich der Logikanteil besser
Unit Tests und getrennt weiter entwickeln.
```javascript
import fetch from 'node-fetch';
const fetchArtistsByName = (name) => {
const headers = {
"Accept": "application/json",
"Authorization": "Bearer BQBg2HM1Q..."
};
return fetch(`https://api.spotify.com/v1/search?q=${name}&type=artist`, {headers})
.then((response) => {
return response.json();
// Konvertierung in ein Javascript Objekt
})
.then((data) => {
return data.artists.items || [];
// herausfiltern der Liste an Künstlern - mit leerer Liste als Default
})
.then((data) => {
return data.map(artistRaw => toArtist(artistRaw));
// Transformation jedes einzelnen Elements aus den geladenen Daten
});
};
```
Mit den Informationen in der rohen JSON-Antwort müssen wir die Liste der `Artist`s per `toArtist ()` transformieren:
* So wird hier die Liste der Images so reduziert, dass im `image`-Feld nur noch ein leerer String oder eine URL
des ersten Bildes steht.
* Alle anderen Felder mit dem gleichen Namen **wie im Schema** werden dann **automatisch** übernommen, alle
anderen ignoriert.
* Der Rückgabewert für `albums` ist eine Funktion, die nur dann aufgerufen wird, wenn tatsächlich `albums` in
der GraphQL-Abfrage angefordert wird.
```javascript
const toArtist = (raw) => {
return {
// füllt mit allen raw-Daten (von ES6 Spread Operator):
// gleichbedeutend mit explizitem
// name: raw.name, ...
...raw,
// else: just takes URL of the first image
        // braucht zusätzliche Logik: Per default eine leere Zeichenfolge, wenn kein Bild vorhanden ist
        // Sonst nur die URL des ersten Bildes
image: raw.images[0] ? raw.images[0].url : '',
        // ... muss die Alben des Künstlers abrufen:
albums: () => {
            // ähnlich wie fetchArtistsByName ()
            // gibt ein Promise Objekt zurück, das asynchron aufgelöst wird!
let artistId = raw.id;
return fetchAlbumsOfArtist(artistId); // has to be implemented, too ...
}
};
};
```
# Zusammenfassung GraphQL Server
Wir haben unseren eigenen GraphQL-Server erstellt, der uns grundlegende Informationen vom Spotify-API-Server
lädt. Jetzt können wir mit dem Laden der Daten zu einem Künstler in Graphiql beginnen:
Beim Aufruf über direktem Link von unserem lokalen Server
[http://localhost:4000/graphql?query=%7B%0A%20%20queryArtists....](http://localhost:4000/graphql?query=%7B%0A%20%20queryArtists%20%0A%20%20%20%20%20(byName%3A%22Red%20Hot%20Chili%20Peppers%22)%20%0A%20%20%7B%0A%20%20%20%20name%0A%20%20%7D%0A%7D%0A)
oder von der Live Demo
[https://spotify-graphql-server.herokuapp.com/graphql?query=....](https://spotify-graphql-server.herokuapp.com/graphql?query=%7B%0A%20%20queryArtists(byName%3A%20%22Red%20Hot%20Chili%20Peppers%22)%20%7B%0A%20%20%20%20name%0A%20%20%7D%0A%7D%0A)
erhalten wir:
![graphiql-artists](https://blog.codecentric.de/files/2016/10/screenshot-graphiql-spotify.png)
Der Quellcode der [Demoversion](https://spotify-graphql-server.herokuapp.com/graphql) findet sich [auf Github](https://github.com/lowsky/spotify-graphql-server)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment