The second of three articles focused on building Fullstack React and GraphQL with Express and Couchbase Server.
- Setting up a NoSQL Couchbase Server (Part 1)
- Building an Express-GraphQL API (Part 2)
- Create Apollo GraphQL Client in React (Part 3)
- Final Source Code
GraphQL with Express
If unfamiliar with GraphQL, take a few minutes to get up to speed using the GraphQL documentation. The following pages should be a good start:
Prerequisites
Create Project Structure
We need to create a directory on your machine for our project, we will call it rage-with-couchbase
:
1 |
mkdir rage-with-couchbase && cd $_ |
mkdir
will create a new directory using the string rage-with-couchbase
for the folder’s name, bash stores that string in a variable we can use immediately named $_
.
we change directories using $_
ensuring we don’t misspell the directory on the concatenated command (it’s bash magic).
Now let’s create a .gitignore
file in the root of our project.
1 |
touch .gitignore && echo "/node_modules/*" >> .gitignore |
touch
will generate a .gitignore
file ignoring all node_modules
directories and sub-directories in our project. This is important because as part of our fullstack React and GraphQL demo project, we will be tracking git changes from the root rage-with-couchbase
directory, but we will have server and client directories with their own package.json
and node_modules
directory.
echo
will add the node_modules/
text inside the .gitignore
file, this will serve to ignore all node_modules directories in the root and all subdirectories.
Creating Our Express Server
Now we will create the directory to store our Express server and we will use npm to manage its packages.
1 |
mkdir couchbase-gql-server && cd $_ && npm init -y |
mkdir
will create a new folder inside the project root specifically for our server using the string couchbase-gql-server
for the name, this is your server project directory.
we change directories and use the $_
(more magic) and then we initialize an npm project using npm init -y
accepting the default values with the -y
flag.
Install Express Server Dependencies
1 |
npm install graphql express express-graphql couchbase && npm install nodemon -D && code . |
Once install is finished, we need to open our project with our editor of choice. I prefer VS Code.
Create Our GraphQL Server
1 |
touch server.js .gitignore && echo "/node_modules/*" >> .gitignore |
Require express/express-graphql/graphql
in our server.js
file:
1 2 3 4 5 |
const express = require('express') const { graphqlHTTP } = require('express-graphql') const { buildSchema } = require('graphql') const couchbase = require('couchbase') |
The first three imports are needed for our GraphQL server and the last import is required for connecting to and querying our Couchbase Server.
Initialize Express and Connect to Our Bucket
Add the following code:
1 2 3 4 5 6 |
const app = express() const cluster = new couchbase.Cluster('couchbase://localhost', { username: 'Administrator', password: 'password' }) const bucket = cluster.bucket('travel-sample') var collection = bucket.defaultCollection(); |
Above we are connecting to our Couchbase Server cluster, authenticating with our user that we set up, and opening our travel-sample
bucket. Never use default usernames and passwords in production applications.
Create Our GraphQL Schema
Adding the code below will define two endpoints that will enable our GraphQL Server to access and retrieve data from our documents inside of our Couchbase Server bucket.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
const schema = buildSchema(` type Airline { id: Int, callsign: String, country: String, iata: String, icao: String, name: String, type: String } input AirlineInput { callsign: String, country: String, iata: String, icao: String, name: String, type: String } type Query { airlinesUK: [Airline], airlineByKey(id: Int!): Airline airlinesByRegion(region: String!): [Airline] } type Mutation { updateAirline(id: Int!, input: AirlineInput): Airline } |
The Query
type specifies which GraphQL queries clients can execute against its own data graph.
We have two endpoints defined by that Query
. One named “airlinesUK” and the other “airlineByKey”. In our React app, we will only use the “airlinesUK” endpoint. I made the “airlineByKey” endpoint simply to show an example of retrieving a single Couchbase document by key. This operation does not use the N1QL query language and therefore does not have any additional overhead. Understanding when and where to use each is important from the perspective of building the API, we wouldn’t want to use a N1QL query to return a single document that we can get by key.
In our GraphQL code, we have an object of type Airline
. This object model the document structure found in our Couchbase travel-sample
bucket where the type is “airline”.
Next, we have an Endpoint named “airlinesUK
“. Notice that the return value of this endpoint is an array of Airline: [Airline]
. This means we will be getting a list of airlines back.
We also have “airlineByKey
” endpoint where we will fetch a single Airline
.
If you remember from our Bucket images above, each document is defined by a key in a format like airline_1234
where 1234
is the id
of the airline.
We will keep this id
in mind when using the NodeJS SDK to fetch our individual airlineByKey
using a simple bucket.get()
method.
Create Our Resolver Implementation for Each Endpoint
Now, that we have defined two queries in our GraphQL-Express API using our schema
object, we need implementation in JavaScript for retrieving the data.
Our React application that we will create will only need the N1QL query named airlinesUK
.
But I wanted to show you how to query without N1QL using the NodeJS SDK’s API using just a key or extending it to use a region (US/UK, etc..), that is the airlineByKey
and airlineByRegion
implementation.
Add the following code to our server.js
file for our N1QL queries:
(In your JS code, ensure to escape backticks with backslashes)
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// Define N1QL Queries const airlinesUkQuery = ` SELECT airline.* FROM `travel-sample` AS airline WHERE airline.type = 'airline' AND airline.country = 'United Kingdom' ` const airlinesByRegionQuery = ` SELECT airline.* FROM `travel-sample` AS airline WHERE airline.type = 'airline' AND airline.country = $REGION ` |
Add the following code to our server.js
file for our GraphQL resolvers:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
const root = { airlinesUK: async () => { const result = await cluster.query(airlinesUkQuery) return result.rows }, /* query getAirlinesUK { airlinesUK { id name callsign country iata icao } } */ airlinesByRegion: async ({region}) => { const options = { parameters: { REGION: region } } const result = await cluster.query(airlinesByRegionQuery, options) return result.rows }, /* query getAirlinesByRegion($region: String!) { airlinesByRegion(region:$region) { id name callsign country iata icao } } { "region": "{{country}}" } */ airlineByKey: async ({id}) => { const result = await collection.get(`airline_${id}`) return result.value }, /* query getAirlineByKey($id: Int!) { airlineByKey(id:$id) { id name callsign country iata icao } } { "id": {{id}} } */ updateAirline: async ({id, input}) => { const result = await collection.get(`airline_${id}`) const newDocument = { ...result.content, callsign: input.callsign ? input.callsign : result.value.callsign, country: input.country ? input.country : result.value.country, iata: input.iata ? input.iata : result.value.iata, icao: input.icao ? input.icao : result.value.icao, name: input.name ? input.name : result.value.name, }; console.log(newDocument) await collection.upsert(`airline_${id}`, newDocument) return newDocument } /* mutation updateExistingAirline($id:Int!, $input:AirlineInput) { updateAirline(id:$id, input:$input){ callsign country iata } } { "id": 112, "input": { "callsign": "FLYSTAR", "country": "United Kingdom" } } */ } |
Note: In the N1QL statement above, you will need to escape the backticks in the FROM line around
with backslashes. Our blog’s code sample does not show those Backslashes, but they are there and they are in the Final Source Codetravel-sample
Just to drive this home, we are using two different methods to query our Couchbase Server.
The first method corresponds to the airlinesUK
endpoint, this is its resolver. We need to return a promise and inside rely on bucket.query
to take a predefined N1QL query that I have broken up line by line in the statement variable for readability. Being able to run this type of SQL query is very powerful for a document database. A lot of the SQL we know transfers over and this is a big relief compared to other document databases that have a brand new API and query language you will need to learn.
The second method corresponds to the airlinesUK
endpoint, this is its resolver. We need to return a promise and inside rely on bucket.get
method and in this case, we are just defining the key of our document. Remember that one of the great things about using a key-value data store is that we can easily pick out a single document with little overhead.
Each of the methods above also tests for query errors and either resolve or reject based on a result
or error
.
Creating Our GraphQL with Express Server
Now that we have everything sorted out for our endpoints and queries, all we need to do is use
our Express server and give it a port to run on, let’s do that now.
Add the following code to the end of our server.js
file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
/* The graphqlHTTP function accepts a schema, rootValue and graphiql among other options for configuring our GraphQL Server */ const serverPort = 4000 const serverUrl = '/graphql' app.use(serverUrl, graphqlHTTP({ schema: schema, rootValue: root, graphiql: true })) app.listen( serverPort, () => console.log(`GraphQL server running: http://localhost:${serverPort}${serverUrl}`) ) |
If you have ever created an Express or Express-GraphQL server, this code should look familiar.
First, we set up our port and GraphQL URL.
Next, we pass in our GraphQL schema and its resolvers and set our graphiql
option to true. (This will give us an IDE to test our GraphQL queries available at localhost:4000/graphql.
Finally, we listen on port 4000 and set a message in the console to indicate our server is running.
Let’s run our server, ensure that your Couchbase:
1 |
node server |
Once we have the GraphQL server running we can test the AirlinesUK
query by pasting the following code into the GraphQL IDE query pane:
1 2 3 4 5 6 7 8 9 10 |
query getAirlinesUK{ airlinesUK { id name callsign country iata icao } } |
As the query indicates it will retrieve all of our UK based airlines:
Next, we will use the airlineByKey
endpoint, in this example, we will also need to create a query variable and paste it into the respective pane:
1 2 3 4 5 6 7 8 9 10 |
query getAirlineByKey($id: Int!) { airlineByKey(id:$id){ id name callsign country iata icao } } |
1 2 3 |
{ "id": 112 } |
And with that in place and we can query again and retrieve a single airline document by key:
With a super simple GraphQL API setup, we are ready to create our react application that will use these endpoints for a master-detail page using the UK airlines in a list component and when we click a particular Airline, another component will show the full details of the airline on the right side of the page.