This article was originally published on the Okta developer blog. Thank you for supporting the partners who make SitePoint possible.
During the past 10 years or so, the concept of REST APIs for web services has become the bread and butter for most web developers. Recently a new concept has emerged, GraphQL. GraphQL is a query language that was invented by Facebook and released to the public in 2015. During the last three years, it has created quite a stir. Some regard it as a new revolutionary way of creating web APIs. The main difference between traditional REST and GraphQL is the way queries are sent to the server. In REST APIs you will have a different endpoint for each type of resource and the response to the request is determined by the server. Using GraphQL you will typically have only a single endpoint, and the client can explicitly state which data should be returned. A single request in GraphQL can contain multiple queries to the underlying model.
In this tutorial, I will be showing you how to develop a simple GraphQL web application. The server will run using Node and Express and the client will be based on Angular 7. You will see how easy it is to prepare the server for responding to different queries. This removes much of the work needed compared to implementing REST-style APIs. To provide an example I will create a service in which users can browse through the ATP Tennis players and rankings.
Build Your Express Server using GraphQL
I will start by implementing the server. I will assume that you have Node installed on your system and that the npm
command is available. I will also be using SQLite to store the data. In order to create the database tables and import the data, I will be making use of the sqlite3
command line tool. If you haven’t got sqlite3
installed, head over to the SQLite download page and install the package that contains the command-line shell.
To start off, create a directory that will contain the server code. I have simply called mine server/
. Inside the directory run
npm init -y
Next, you will have to initialize the project with all the packages that we will be needing for the basic server.
npm install --save express@4.16.4 cors@2.8.4 express-graphql@0.6.12 graphql@14.0.2 sqlite3@4.0.2
Import Data to Your Express Server
Next, let’s create the database tables and import some data into them. I will be making use of the freely available ATP Tennis Rankings by Jeff Sackmann. In some directory on your system clone the GitHub repository.
git clone https://github.com/JeffSackmann/tennis_atp.git
In this tutorial, I will only be using two of the files from this repository, atp_players.csv
and atp_rankings_current.csv
. In your server/
directory start SQLite.
sqlite3 tennis.db
This will create a file tennis.db
that will contain the data and will give you a command line prompt in which you can type SQL commands. Let’s create our database tables. Paste and run the following in the SQLite3 shell.
CREATE TABLE players(
"id" INTEGER,
"first_name" TEXT,
"last_name" TEXT,
"hand" TEXT,
"birthday" INTEGER,
"country" TEXT
);
CREATE TABLE rankings(
"date" INTEGER,
"rank" INTEGER,
"player" INTEGER,
"points" INTEGER
);
SQLite allows you to quickly import CSV data into your tables. Simply run the following command in the SQLite3 shell.
.mode csv
.import {PATH_TO_TENNIS_DATA}/atp_players.csv players
.import {PATH_TO_TENNIS_DATA}/atp_rankings_current.csv rankings
In the above, replace {PATH_TO_TENNIS_DATA}
with the path in which you have downloaded the tennis data repository. You have now created a database that contains all ATP ranked tennis players ever, and the rankings of all active players during the current year. You are ready to leave SQLite3.
.quit
Implement the Express Server
Let’s now implement the server. Open up a new file index.js
, the main entry point of your server application. Start with the Express and CORS basics.
const express = require('express');
const cors = require('cors');
const app = express().use(cors());
Now import SQLite and open up the tennis database in tennis.db
.
const sqlite3 = require('sqlite3');
const db = new sqlite3.Database('tennis.db');
This creates a variable db
on which you can issue SQL queries and obtain results.
Now you are ready to dive into the magic of GraphQL. Add the following code to your index.js
file.
const graphqlHTTP = require('express-graphql');
const { buildSchema } = require('graphql');
const schema = buildSchema(`
type Query {
players(offset:Int = 0, limit:Int = 10): [Player]
player(id:ID!): Player
rankings(rank:Int!): [Ranking]
}
type Player {
id: ID
first_name: String
last_name: String
hand: String
birthday: Int
country: String
}
type Ranking {
date: Int
rank: Int
player: Player
points: Int
}
`);
The first two lines import graphqlHTTP
and buildSchema
. The function graphqlHTTP
plugs into Express and is able to understand and respond to GraphQL requests. The buildSchema
is used to create a GraphQL schema from a string. Let’s look at the schema definition in a little more detail.
The two types Player
and Ranking
reflect the contents of the database tables. These will be used as the return types to the GraphQL queries. If you look closely, you can see that the definition of Ranking
contains a player
field that has the Player
type. At this point, the database only has an INTEGER
that refers to a row in the players
table. The GraphQL data structure should replace this integer with the player it refers to.
The type Query
defines the queries a client is allowed to make. In this example, there are three queries. players
returns an array of Player
structures. The list can be restricted by an offset
and a limit
. This will allow paging through the table of players. The player
query returns a single player by its ID
. The rankings
query will return an array of Ranking
objects for a given player rank.
To make your life a little easier, create a utility function that issues an SQL query and returns a Promise
that resolves when the query returns. This is helpful because the sqlite3
interface is based on callbacks but GraphQL works better with Promises. In index.js
add the following function.
function query(sql, single) {
return new Promise((resolve, reject) => {
var callback = (err, result) => {
if (err) {
return reject(err);
}
resolve(result);
};
if (single) db.get(sql, callback);
else db.all(sql, callback);
});
}
Now it’s time to implement the database queries that power the GraphQL queries. GraphQL uses something called rootValue
to define the functions corresponding to the GraphQL queries.
const root = {
players: args => {
return query(
`SELECT * FROM players LIMIT ${args.offset}, ${args.limit}`,
false
);
},
player: args => {
return query(`SELECT * FROM players WHERE id='${args.id}'`, true);
},
rankings: args => {
return query(
`SELECT r.date, r.rank, r.points,
p.id, p.first_name, p.last_name, p.hand, p.birthday, p.country
FROM players AS p
LEFT JOIN rankings AS r
ON p.id=r.player
WHERE r.rank=${args.rank}`,
false
).then(rows =>
rows.map(result => {
return {
date: result.date,
points: result.points,
rank: result.rank,
player: {
id: result.id,
first_name: result.first_name,
last_name: result.last_name,
hand: result.hand,
birthday: result.birthday,
country: result.country
}
};
})
);
}
};
The first two queries are pretty straightforward. They consist of simple SELECT
statements. The result is passed straight back. The rankings
query is a little more complicated because a LEFT JOIN
statement is needed to combine the two database tables. Afterward, the result is cast into the correct data structure for the GraphQL query. Note in all these queries how args
contains the arguments passed in from the client. You do not need to worry in any way about checking missing values, assigning defaults, or checking the correct type. This is all done for you by the GraphQL server.
All that is left to do is create a route and link the graphqlHTTP
function into it.
The post Build a Simple Web App with Express, Angular, and GraphQL appeared first on SitePoint.
by Holger Schmitz via SitePoint
No comments:
Post a Comment