Skip to content

Commit e5a0c01

Browse files
authored
Merge pull request #274 from neo4j/northwind-api
Northwind API how to
2 parents 95ec337 + dfeb00a commit e5a0c01

File tree

3 files changed

+371
-1
lines changed

3 files changed

+371
-1
lines changed

modules/ROOT/content-nav.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
* *How-To*
5757
5858
* xref:driver-configuration.adoc[]
59+
* xref:graphql-modeling.adoc[]
5960
6061
* *Products*
6162
Lines changed: 368 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,368 @@
1+
[[northwind-api]]
2+
= GraphQL modelling for the Northwind data set
3+
:description: This tutorial builds an API around the Northwind sample data set with the Neo4j GraphQL Library.
4+
5+
This tutorial uses the Neo4j GraphQL Library to build an API for the Northwind sample dataset.
6+
7+
The Northwind set includes but is not limited to data about products, suppliers, orders and customers.
8+
This model lends itself for a webshop API.
9+
10+
11+
== Prerequisites
12+
13+
. Set up a new AuraDB instance.
14+
Refer to link:https://neo4j.com/docs/aura/getting-started/create-instance/[Creating a Neo4j Aura instance].
15+
. Populate the instance with the Northwind data set.
16+
+
17+
If you have completed the GraphQL and Aura Console getting started guide and would like to get rid of the example nodes you have created there, run the following in **Query** before populating your data base with the Northwind set:
18+
+
19+
[source,cypher]
20+
----
21+
MATCH (n) DETACH DELETE n;
22+
----
23+
+
24+
[CAUTION]
25+
====
26+
This Cypher query deletes all data in your database.
27+
====
28+
+
29+
.. In Aura, select **Learning**, then **Beginner** under **Getting started**.
30+
.. Select the **Learn the basics** tile and scroll to page 4/11 in the left side menu.
31+
.. Trigger the import with **Get the Northwind datset** and then **Run import** on the right hand side.
32+
33+
== Goal
34+
35+
A webshop API which connects to the Northwind data set should be able to:
36+
37+
* Create new customers
38+
* Place orders
39+
* Calculate prices for orders
40+
* Filter products by supplier and category
41+
42+
See xref:#_use_the_api[] for example implementations.
43+
44+
45+
== Create the GraphQL Data API
46+
47+
See xref:getting-started/graphql-aura.adoc[] for steps on how to do this.
48+
For the purpose of this tutorial, make sure to **Enable introspection** and **Enable field suggestions**.
49+
50+
51+
=== Type definitions
52+
53+
Make the relevant nodes and relationships available by using these type definitions:
54+
55+
[source, graphql, indent=0]
56+
----
57+
type Customer @node {
58+
contactName: String!
59+
customerID: ID! @id
60+
orders: [Order!]! @relationship(type: "PURCHASED", direction: OUT)
61+
}
62+
63+
type Order @node {
64+
orderID: ID! @id
65+
customer: [Customer!]! @relationship(type: "PURCHASED", direction: IN)
66+
products: [Product!]! @relationship(type: "ORDERS", direction: OUT, properties: "ordersProperties")
67+
}
68+
69+
type Product @node {
70+
productName: String!
71+
category: [Category!]! @relationship(type: "PART_OF", direction: OUT)
72+
orders: [Product!]! @relationship(type: "ORDERS", direction: IN, properties: "ordersProperties")
73+
supplier: [Supplier!]! @relationship(type: "SUPPLIES", direction: IN)
74+
}
75+
76+
type Category @node {
77+
categoryName: String!
78+
products: [Product!]! @relationship(type: "PART_OF", direction: IN)
79+
}
80+
81+
type Supplier @node {
82+
supplierID: ID! @id
83+
companyName: String!
84+
products: [Product!]! @relationship(type: "SUPPLIES", direction: OUT)
85+
}
86+
87+
type ordersProperties @relationshipProperties {
88+
unitPrice: Float!
89+
quantity: Int!
90+
}
91+
----
92+
93+
Navigate to the link:https://studio.apollographql.com/sandbox/explorer[Apollo Studio] website and paste your GraphQL Data API URL to the **Sandbox** input.
94+
Use the cog icon and add `x-api-key` and the API key for your data API under **Shared headers** and **Save**.
95+
96+
=== Make sure the API is working
97+
98+
Verify that the relevant parts of the Northwind data set are accessible:
99+
100+
[source, graphql, indent=0]
101+
----
102+
query {
103+
categories {
104+
categoryName
105+
}
106+
}
107+
----
108+
109+
You should see as the **Response**:
110+
111+
[source, json, indent=0]
112+
----
113+
{
114+
"data": {
115+
"categories": [
116+
{
117+
"categoryName": "Beverages"
118+
},
119+
{
120+
"categoryName": "Condiments"
121+
},
122+
{
123+
"categoryName": "Confections"
124+
},
125+
{
126+
"categoryName": "Dairy Products"
127+
},
128+
{
129+
"categoryName": "Grains/Cereals"
130+
},
131+
{
132+
"categoryName": "Meat/Poultry"
133+
},
134+
{
135+
"categoryName": "Produce"
136+
},
137+
{
138+
"categoryName": "Seafood"
139+
}
140+
]
141+
}
142+
}
143+
----
144+
145+
== Use the API
146+
147+
The following sections provide simple examples of how to use the API in a webshop scenario.
148+
149+
150+
=== Creating new customers
151+
152+
The following mutation creates a new customer by the name of "Jane Doe":
153+
154+
[source, graphql, indent=0]
155+
----
156+
mutation {
157+
createCustomers(
158+
input: [{
159+
contactName: "Jane Doe"
160+
}]
161+
) {
162+
customers {
163+
contactName
164+
}
165+
}
166+
}
167+
----
168+
169+
To make it generic, you can use a link:https://graphql.org/learn/queries/#variables[GraphQL variable] to set the contactName dynamically:
170+
171+
[source, graphql, indent=0]
172+
----
173+
mutation CreateCustomer($contactName: String!) {
174+
createCustomers(
175+
input: [{
176+
contactName: $contactName
177+
}]
178+
) {
179+
customers {
180+
contactName
181+
customerID
182+
}
183+
}
184+
}
185+
----
186+
187+
188+
=== Placing an order
189+
190+
To place an order, create a new order node that is linked to a number of product nodes and a customer node:
191+
192+
[source, graphql, indent=0]
193+
----
194+
mutation {
195+
createOrders(
196+
input: {
197+
customer: {
198+
connect: { where: { node: { contactName: { eq: "Jane Doe" } } } }
199+
}
200+
products: {
201+
connect: {
202+
edge: { unitPrice: 23.25, quantity: 5 }
203+
where: { node: { productName: { eq: "Tofu" } } }
204+
}
205+
}
206+
}
207+
) {
208+
orders {
209+
orderID
210+
}
211+
}
212+
}
213+
----
214+
215+
To place an order, the customer and product information must already be known or collected.
216+
A shopping basket on the client side typically processes and displays this information.
217+
218+
219+
=== Calculate prices for orders
220+
221+
To calculate order prices, query the `order` operation field and filter the orders by using the `orderID` filter.
222+
Then access the relationship properties `quantity` and `unitPrice` from the relationships `ORDERS` that connect the `Order` and the `Product` node:
223+
224+
[source, graphql, indent=0]
225+
----
226+
query {
227+
orders(where: { orderID: { eq: "6a5572bb-41fb-4263-913c-69c678c04766"} }) {
228+
products {
229+
productName
230+
}
231+
orderID
232+
productsConnection {
233+
edges {
234+
properties {
235+
quantity
236+
unitPrice
237+
}
238+
}
239+
}
240+
}
241+
}
242+
----
243+
244+
The result looks like this:
245+
246+
[source, json, indent=0]
247+
----
248+
{
249+
"data": {
250+
"orders": [
251+
{
252+
"products": [
253+
{
254+
"productName": "Tofu"
255+
}
256+
],
257+
"orderID": "6a5572bb-41fb-4263-913c-69c678c04766",
258+
"productsConnection": {
259+
"edges": [
260+
{
261+
"properties": {
262+
"quantity": 5,
263+
"unitPrice": 23.25
264+
}
265+
}
266+
]
267+
}
268+
}
269+
]
270+
}
271+
}
272+
----
273+
274+
The product of `quantity` and `unitPrice` is the total cost, which in this case is 116.25.
275+
276+
Note that there is no `discount` field on the `ORDERS` relationship and it is unclear how taxation works in this scenario.
277+
278+
279+
=== Filter products
280+
281+
To filter products by category and supplier, first query for the `categoryName`s and supplier `companyName`s:
282+
283+
[source, graphql, indent=0]
284+
----
285+
query {
286+
categories {
287+
categoryName
288+
}
289+
suppliers {
290+
companyName
291+
}
292+
}
293+
----
294+
295+
Subsequent queries can now yield a filtered product list.
296+
For products of a certain category:
297+
298+
[source, graphql, indent=0]
299+
----
300+
query {
301+
products(where: {categoryConnection: {all: {node: {categoryName: {eq: "Produce"}}}}}) {
302+
productName
303+
}
304+
}
305+
----
306+
307+
Result:
308+
309+
[source, json, indent=0]
310+
----
311+
{
312+
"data": {
313+
"products": [
314+
{
315+
"productName": "Uncle Bob's Organic Dried Pears"
316+
},
317+
{
318+
"productName": "Tofu"
319+
},
320+
{
321+
"productName": "Rössle Sauerkraut"
322+
},
323+
{
324+
"productName": "Manjimup Dried Apples"
325+
},
326+
{
327+
"productName": "Longlife Tofu"
328+
}
329+
]
330+
}
331+
}
332+
----
333+
334+
Similarly, a filter by supplier looks like this:
335+
336+
[source, graphql, indent=0]
337+
----
338+
query {
339+
products(where: {supplierConnection: {some: {node: {companyName: {eq: "New England Seafood Cannery"}}}}}) {
340+
productName
341+
}
342+
}
343+
----
344+
345+
Result:
346+
347+
[source, json, indent=0]
348+
----
349+
{
350+
"data": {
351+
"products": [
352+
{
353+
"productName": "Boston Crab Meat"
354+
},
355+
{
356+
"productName": "Jack's New England Clam Chowder"
357+
}
358+
]
359+
}
360+
}
361+
----
362+
363+
364+
== Links
365+
366+
* See xref:directives/autogeneration.adoc#type-definitions-autogeneration-id[`@id`] for more on how to handle unique identifiers with the GraphQL Library
367+
* See xref:directives/database-mapping.adoc#_relationshipproperties[`@relationshipProperties`] for details on relationship properties accessed with the GraphQL Library
368+
* See xref:filtering.adoc[] for more information about how to apply filters with the GraphQL Library

modules/ROOT/pages/types/relationships.adoc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ type Person @node {
147147
----
148148

149149
`queryDirection` can have the following values:
150+
150151
* `DIRECTED`: only directed queries can be performed on this relationship.
151152
* `UNDIRECTED`: only undirected queries can be performed on this relationship.
152153

@@ -335,4 +336,4 @@ type Post @node {
335336
----
336337

337338
The relationship at `User.posts` is considered a "many" relationship, which means it should always be of type `NonNullListType` and `NonNullNamedType`.
338-
In other words, both the array and the type inside of a "many" relationship should have a `!`.
339+
In other words, both the array and the type inside of a "many" relationship must have a `!`.

0 commit comments

Comments
 (0)