Getting Started with GraphQL

得到started with graphql

This is a complimentary blog post to a video from theShopifyDevs YouTube channel. In this article, Chuck Kosman, a Launch Engineer at Shopify Plus, walks you through an introduction of using GraphQL with Shopify. We cover:

  1. What GraphQL is
  2. What tools you can use to get up and running with GraphQL
  3. A tutorial of making GraphQL requests against a client’s store

What is GraphQL?

Let's start by answering the question, what is GraphQL? If you were to go toGraphQL.org, you'd find this quote right at the top. GraphQL is described as, “A query language for APIs and a runtime for fulfilling those queries with your existing data.”

"GraphQL is described as, 'A query language for APIs and a runtime for fulfilling those queries with your existing data.'"

Query languages are expressive, highly structured ways to ask for data, and you might be familiar with them in the context of databases.

Here, we're talking about using query languages for an API. If we were to translate this into a more functional definition, you'd end up with something like this: “GraphQL is an expressive, structured way to ask for data from another application and the underlying system to fetch that data.”

You're probably familiar with RESTful APIs as a way to ask for data from another application. But GraphQL is really being hailed as the better REST. REST solved a lot of problems of its predecessors, but GraphQL is specifically designed to address some of the shortcomings of REST when it comes to modern applications. One of the principal problems with REST in modern applications is that they overfetch or underfetch. This means that in a single request, they either get too much data or they don't get enough.

Let’s use a restaurant, something that's long been used as an analogy for APIs, to explore these concepts a little further.

The ice cream analogy: exploring concepts relating to APIs

I'm going to use an ice cream shop as my restaurant here and I'm going to start with overfetching.

On Shopify, if I wanted to know something about an order, I would have to use this end point on the admin REST API, and when I plug in an order ID to it, I get everything I could ever want to know about a single order.

In terms of a restaurant or an ice cream shop specifically, this might be an ice cream truck that only has preconfigured sundaes on the menu that contain ingredients that you don't necessarily want and there's no combination that you really do want. The restaurant has put all of this time and effort into putting the toppings on and arranging things as they specified that they would. But, if there's something that your client doesn't like or need, then it's on you to remove those. This can be quite inefficient for both parties.

得到started with graphql: overfetching

In contrast, underfetching is when you don't get enough data from a single request. This often comes up when you're looking for deeply nested data.

Here's an example in Shopify where I went to get at the metafields of a certain set of products. I first start by issuing aGETrequest to all products. But that's not all the data that I really need.

Then I had to go use a different endpoint, plug in the IDs, and then finally get to the metafields that I want. You may have heard this characterized as then+1problem. I issued one request to get the entire list of products and then I have to issuennumber of requests to get at each resource that I'm interested in at the end. So in terms of our ice cream shop analogy, this might be something like where I get some ice cream and each time I want a topping, I have to pass the ice cream back and forth and I can only get one topic at a time.

Very inefficient, very inflexible.

You might also like:How to Get More App Downloads in the Shopify App Store.

得到started with graphql: screengrab of underfetching

Enter GraphQL. You supply the query language data that you would like along with your request and you get back only the data that you ask for. What's interesting here is you'll notice that I'm using aPOSTrequest to this end point on theadmin/API/GraphQL.json, and along with it I'm supplying a body of this nested structure of data. This particular nested structure of data is a pseudocode example of asking for some fields on products by a particular product ID.

So in this case, let's say I only wanted to examine thetitle,handle, and thenvariantsfields belonging to products that I might be interested in. I could populate them in the...as shown below. What I get back ends up being only the data that I asked for.

得到started with graphql: what GraphQL can do for you

In the context of our ice cream shop analogy, I would go up to the counter, I specify exactly what flavors and toppings I want, and I get exactly that in one efficient, flexible request.

Because I specify exactly what data I want in each request, it's clear to both the server, that which is filling the query, and the client, myself, my colleagues, and the rest of the application, what data is being used. This is very advantageous for clarity and moving fast. It enables rapid front end iteration because there's only one single endpoint. I don't have to juggle a bunch of different endpoints as a front end developer, and it's how I structure my request that dictates what data I get back, not what endpoints I have to call in sequence.

Lastly, you might be wondering, well, if there's only one endpoint, how do I know what I can even ask for?

This is really the heart beat of GraphQL, this transparent documentation of its capabilities through what's called a schema. We specifically call ita strongly-typed schema, meaning that the patterns of data that you get back are defined by underlying types. And so it's very clear, even as you're typing or formatting your request if you're making an error or not.

Ultimately, all of these things lead to a more efficient, powerful, and flexible successor to REST.

Tools to start making GraphQL requests with Shopify

Now that we know a little bit about what GraphQL is and how it compares to REST, let's start talking about the tools that you can use to start making GraphQL requests on Shopify.

It could be as simple as acURLrequest, but you'd miss out on this strongly-typed schema, one of the most powerful features of GraphQL. However, we can get that using something called the GraphiQL IDE or Integrated Development Environment. We have an implementation of this for both the GraphQL admin API and the storefront API. We'll talk about the distinction between those in a moment, but we're going to stick with the admin API implementation.

What you’ll need to get started

There is a piece of documentation called theShopify Admin API GraphiQL Explorer. You can actually start playing around with it as read only right here in the documentation, but if you'd like to follow along in this tutorial, I'm going to suggest that youinstall Shopify's GraphiQL app.

You'll need a store to install this on, so if you don't already have a partner development store or a trial store, no worries. There's great documentation on exactly how to do this. If you need to get a store up and running on which to install it, there's another piece of documentation calledMake Your First GraphQL Requestin our Shopify dev tutorials.

得到started with graphql: how to get started documentation

And right at the top, there's only two steps that you need to follow of these three. One is creating a partner account. These are what folks in our ecosystem use to make merchant solutions. It's totally free.

With that partner account, you will create a development store or log into an existing one if you have one. We won't need this last part for our tutorials using GraphiQL, but you could optionally follow along here if you prefer to work with a standalone HTTP client. And we'll see what that looks like as well in a moment.

So if you don't have a store, get one, and I highly suggest populating some test data so that when you go to make queries, you're going to get some meaningful data back. You could stick with products and variants, but you can go as far as products, customers, and orders if you so desire. Those are kind of the big three.

的Shopify GraphQL Integrated Development Environment (IDE)

Anyway, I'm going to go back to this Shopify Admin API GraphiQL Explorer and I'm going to install it on a store that I already have set up. I'm going to say install Shopify's GraphiQL app following this link. I happen to have a store already,getting-started-with-graphql.myshopify.com, and I'm going to select that from my browser's autocomplete here.

And this is where you're going to define what scopes this app has access to. If you know the store isn't being used for production, you can safely select all of the scopes. If this is already a production store, I highly suggest you use a development or test store instead, and be very careful, because this app can read and write. So be cautious in selecting your selected scopes.

For this tutorial, you'll really only need products in terms of what I'm going to show, so I'm going to saySelect Allbecause it makes things easier and I'm going to sayInstall. It's going to prompt me to say, “Do you want to grant this app all of these different scopes?” I'm going to sayYes.

得到started with graphql: installing

Once it's finished installing, you'll see an interface like this, a left hand column with this shop name JSON formatted thing and this right-hand column that's blank for now. We'll be using this for the tutorial, but we'll flip back to other methods of working with GraphQL just for the time being.

You might also like:Faster Retrieval of Shopify Metafields with GraphQL.

The standalone HTTP client

You could also use, as I mentioned, a standalone HTTP client. The reason that we're using the GraphiQL IDE for this tutorial is we don't have to specify the endpoint. Fortunately, there's only one in GraphQL, that's one of its wonderful advantages.

But we don't also have to set up the headers either. I'll show you what those headers look like if this is your preferred method of working. You’ll need two headers. You'll need to spin up a private app and you'll need that app's password to pass in as the authentication token under this header name. You’ll also need to specify the content type asapplication/JSON.

有一个application/GraphQLthat some folks get confused about. There are subtle differences and you'll usually want to useapplication/JSONwhen working with a standalone HTTP client. If it doesn't work, switch toapplication/GraphQL. If you need any more guidance on this, you forgot where the slide was, or you want to read this full-on, refer back to thatMake Your FirstGraphQL Requestpiece of documentation. They cover using GraphiQL and in addition to that, they cover using a standalone HTTP client.

There's actually a really awesome blog post for a full tutorial onsetting up one called insomnia. And there's a kit that comes with some preloaded queries that you can start using to get up and running right away. The headers that you'll need and some sample requests are detailed throughout this document. So this is an excellent resource to come back to after this tutorial.

We're going to walk through some elements of this, as well. Last but not least, if you start working at scale, the GraphQL IDE is not going to work for you, this is really just kind of a way to test things. And you wouldn't use a HTTP client to do things in production either, you'd build on a full-on app.

Working at scale: building with GraphQL client libraries

当你去做一个全面的应用,我强烈recommend looking into GraphQL client libraries. Because of the predictability that comes out of this strongly-typed schema of GraphQL, you can layer a lot of developer tools on top of that for rapid and awesome development. You may have heard of Apollo or Relay as popular client libraries, but there are others and it depends on what languages, frameworks, and platforms you're targeting.

"Because of the predictability that comes out of this strongly-typed schema of GraphQL, you can layer a lot of developer tools on top of that for rapid and awesome development."

I briefly touched on that there are two different APIs that you might work with using GraphQL. They are the admin APIs, both REST and GraphQL. Both are used for developing apps and integrations for the Shopify admin. It’s like extending the back office of Shopify, if you want to think of it that way. However the storefront API is for customer-facing purchase experiences, a very different purpose.

得到started with graphql: screenshot from video explaining admin API

And we have software development kits or SDKs to target the web, Android, iOS, and even in-game experiences via Unity. In addition to the fact that GraphQL is the only one available

for storefront-to-storefront API, there are clearly advantages of using GraphQL over REST. In fact, there are certain things you can do using the GraphQL flavor of the admin API that you can't do on the REST API.

In other words, Shopify is really betting on GraphQL. We see the benefits of GraphQL in comparison to RESTful APIs.

What you can accomplish with GraphQL admin APIs

In the past, and continuing now and in the future, there will be certain things that you should expect you can only do on the GraphQL admin API. Some of those listed out here in chronological order of their release:

  • There's a way to bulk adjust inventory on the GraphQL admin API that is many, many more times efficient than the REST API could ever be on the same operation. This is really good forinventory managementat scale.
  • The translations API is GraphQL only.
  • Product media, so being able to not just do images but videos and 3D models, is GraphQL.
  • Order editing, a long requested feature, is GraphQL only on the admin API.
  • As of this post, the duties and taxes APIs and developer preview are GraphQL as well.

你应该d expect that there absolutely will be more features that will be only on GraphQL in the future. You can check these out on ourdeveloper changelogif you want to stay up to date with our Shopify API releases.

You might also like:GraphQL vs REST: How One Shopify Partner Increased Performance and Reliability.

Applying your knowledge: using GraphQL with Shopify

Now that we've got our tools in place, let's start making requests to our client’s Shopify store using the GraphiQL IDE. I'm going to flip over to my store here in the browser, right where I left off with the GraphiQL IDE installation and you should see something that looks a little bit like this:

得到started with graphql: main screen of graphQL

The first thing I'm going to do here is actually going to format this a little bit more nicely, in order to make it look a little bit more like how folks tend to work with this:

{
shop {
name
}
}

You can see that the query language of GraphQL, even just from this starter example here, looks a little bit like a nested structure of JSON. I'm just going to pressPlay. This is how I'm going to say, “Send a request”, essentially to get at what I need. So I’m going to clickExecute query, and what I get back is JSON with two properties.

One is calleddata, and you'll notice that the structure of the data that comes back exactly mirrors the request format that I put in. So I have ashopproperty, and the value of that property is an object, and that object has one key called"name"that I've requested and it so happens that my shop name here is"Getting Started with GraphQL".

得到started with graphql: setting up the store

This extension is actually talking to us about how much this query costs. If you've worked with Shopify's APIs before, particularly the REST API, you know that there is a cap on how many requests you can issue at any given time and how fast those requests leak from this bucket. We call it a leaky bucket model.

GraphQL does have the same limitations, but it is through the cost of the query itself, not the call. The end result of this is that GraphQL ends up being way more efficient.

Remember that overfetching example? There's a lot of computation going on in the background of things that you may not necessarily need. GraphQL is computing the cost of what you requested just based on what you requested, so it is massively more efficient. What else can I query for here?

I can pressEnterand start typing any letter, sayC货币代码。当我这样做时,我得到一个autocomplete of the things that I can query for onshop. I'll be more specific, they're not just things, we call them fields. A field is just the data that you're interested in.

The reason that GraphiQL can do this is because of this strongly-typed schema. It knows the capabilities of the API and it knows exactly what fields belong on which nodes. We'll talk about nodes in just a second here.

If I were to say, tell me about the currency code of the shop and I pressplayagain, there I have currency, I have to set up the store in USD.

Beyond just typing in autocomplete, how do I know what the capabilities of this API are? What is this schema that we've been talking about that's strongly-typed? In this GraphiQL IDE and in standalone HTTP clients that have good GraphQL capabilities, you'll see something that either looks like docs, in this particular instance, or schema.

This tells me exactly what I can do, and it starts out a little bit mysterious at first if you're new to GraphQL.

得到started with graphql: exploring your schema

There's two things I can do at the most basic level. I can:

  1. Read data, which is a query.
  2. I can make a mutation. A mutation is for writing operations.

Comparing this to CRUD architectures in REST, reading is all that a query does. Mutations are everything else; that's creating, updating, and deleting.

Querying data with GraphQL

Now, if I omit the wordquery, I'm implicitly making a query. So if I actually type inqueryhere and pressplay, I'll get the same exact thing. We're making queries to start and we'll move on to mutations in a bit.

得到started with graphql: querying

接下来的问题就变成了,“我怎么知道shopis something that I can get from theQueryRoot?” In other words, my entry point into querying for data. That's theQueryRoot.

I can explore the schema to get exactly that. A query always returns a type ofQueryRoot. If I click onQueryRoot,re's a lot of information here. There's a lot of things I could query, and this is the schema. This is the thing that defines what I can and can'tqueryfor. It's in alphabetical order. Let’s scroll all the way down to S forshop. This is the schema's definition ofshop. It says, “Returns ashopresource corresponding to the access token used in the request.” I haven't actually explicitly supplied an access token, that's kind of the beauty of using GraphiQL.

得到started with graphql: exploring the shop query root

So, what does this mean when it comes to a schema? What doesshop:shop!mean?

It means that if I were to query for ashopfield, I will get atypeback as denoted byShop. That's how I know what fields I can query for underneathShop. The exclamation point means non-nullable, I have to get something back.

So, how do I know that name and currency code are available onShop?

If I click on theShoptype, these are all the things that I can get onShop. If I were to scroll down, there's quite a lot. This is where I got currency code. What does currency code return? It returns a currency code type (CurrencyCode). What doesnamereturn?name返回一个string.

This is the power of GraphQL's strongly-typed schema and the tools that reside on top of it.

I can explore the scheme in real time to see exactly what I can get at. And I know exactly, very predictably what data I can and can't get and what format it's going to be as I explore around. For most purposes, you might useShopevery now and then in production, but you want to start really digging into some of the data entities on the store.

Let’s do an example with products.

You might also like:的Shopify App CLI Tool: Build Apps Faster.

A query example with products

I'm going to structure my query as I go, and compare it to the schema just so

that you can see what's going on. I’ll also take a look at a visualization of how to mentally model these types related to one another in the schema.

I'm going to get rid ofquerybecause getting rid of the wordqueryactually implicitly makes a query. Go ahead and get rid of everything, making sure to preserve the curly braces. I'm inQueryRoothere, so I'm going to probably be interested inproducts.

得到started with graphql: querying for products

OK, soproductsis a little bit more complicated.

I have the fieldproductsand then I have this parentheses to sayfirst,after,last,before,sortKey, all this stuff. Eventually, it says I get returned a type calledProductConnection.

There’s a lot of terminology here, so let's start from the ground up.

If I useproductshere and autocomplete already knowing thatproductsis something I can query for on theQueryRoot. I'm going to open the curly braces again. It's expecting something else here. Well, what do we want onproducts?

Let's say, I wanted theirtitle. Oh, weird. As soon as I typedtitle, that's not something that's coming up. So, what's going on here?

If I pressplay,fieldstitle doesn't exist on product connection, thank you, strongly-typed schema. That’s awesome. But, it's something onProductConnection!卡尔lededgespops up, and I get an array of[ProductEdge!]!s. So, let’s just start with that,edges. OK, that's going to have some fields on it, so I'll click on[ProductEdge!]!to see what exactly that does.

There’s acursorand anodeand it seems likenode返回一个producttype, so that's probably what I want. And then onnode,se are probably things you're more familiar with onproducttypes. Let’s say I just want theIDand thetitle.

I'll pressplay我将得到一个错误:you must provide one of first or last.

得到started with graphql: mainscreen in GraphQL with the root

OK, so what does that mean? It's expecting something onproductsto qualifyfirstorlast. Again, with strongly-typed schema, it's very informative.

What isfirstorlast? Just like in REST, you can't get every single list product just by issuing the get all products endpoint, it's actually paginated. We use cursor based pagination at the recommendation of the GraphQL specification.

That aside, let's take a look at how to do this. To supply what's called an argument, so a qualifier on a request, you must open parentheses. It's actually already saying, “Here are the different ones that you can do”. I'm just going to do the simplest possible one of saying I want thefirst. What is thefirst? Well, I have to provide an integer. So I'm going to say I want the first 10 products. We’ll ignore theedgesandnodething in the middle for now, but we'll get into it in just a moment.

If I pressplay, now we've got really interesting stuff. We havedata, which returns an object with theproductsproperty. This, in turn, returns an object ofedges, which is an array of objects, each containing anodeobject and eventually, the data that I requested. So I have just theIDand thetitlein each product.

得到started with graphql: querying for products to retrieve all the data

How much does that cost? It costs 12 points.

What does that mean? Well, in GraphQL you have a maximum bucket size of 1,000 points and that replenishes at 50 points per second.

Contrast this to 40 API REST calls that you can have in a bucket on the RESTful API that regenerates at two calls per second. The net result: GraphQL is way more efficient than REST, even without all the other goodies on top of this.

GraphQL: exploring the terminology

Let’s come back to this weirdedgesandnodeterminology. What is that? When we talk about GraphQL, we're talking about, for the sake of the API, modeling your data as a graph, a visual representation of how different pieces of data connect to one another.

So, let's talk a little bit about GraphQL terminology and how it's going to help you navigate your work.

As soon as you start working with lists of things, you're going to run intoedgesandnodes all the time. Let’s just back up a step and talk about what those are.

This is essentially a stripped down version of the query that I just wrote: both the code over here and a visual representation of what's going on. I'm entering at theQueryRootand attaching thatQueryRootto our products. Those products have properties on them. I'm querying fortitle. In graph theory, these individual entities are what we callnodesand we call the relationships between entitiesedges.

得到started with graphql: infographic, root with products but no nodes

This is why when I go at the root level and I want to know aboutproducts, I have to say, “Get me all of the relationships toproducts.” I'm saying get me all of theedges. Then, at the ends of thoseedges, tell me about the entities at the ends of thoseedges. Those arenodes.

You'll see all the time when you start working with plural requests, such as getting a list of things, you'll get in the habit of sayingedges,node, and then getting the thing at the end.

Let’s take this a step further: deeply nested queries. We talked about underfetching before, so let's say I want all of the variants on these first 10 products as well. It just so happens that if I type the letterV,variantsis something on here. There's a list ofvariantsthat correspond toproducts. I know that I'm going to have to sayfirst, so I'll just say 10 again arbitrarily.

得到started with graphql: deeply nested queries

That returns a list ofedges, and at the end of thoseedges,re's the variantnode. Let’s say you want theIDand thetitleof those variants. I'll pressplayagain. Awesome. Again, the structure of the data that comes back exactly mirrors the structure of the data how I requested it.

So, I have data and thenproductscomes back as an object andedgesas an array. Each node has theIDin the title as requested and all of the variant information, but only what I requested: just theIDandtitleof each variant. I could extend this withpriceand so on by looking at the schema to see what data is available on variants.

Just flipping back to this mental model, all that I've done is I've taken the same request and I've extended it a little bit.

得到started with graphql: infographic, root with products and nodes

Products have a relationship to variants and product nodes have a relationship to variant nodes. Any time there's a relationship in a graph model, we call that an edge. That’s the line between the two. Each product has some variants at the end of it. In this case, I'm only saying two, just for the sake of making the diagram a little bit shorter.

得到started with graphql: giff of diagram with the nodes being added to the root

To go back to our visual depiction of what we did here by adding variants under products, we started here with getting the product nodes attached to thisQueryRoot.

We know that there's a relationship betweenvariantsandproducts. So inside of this product node, there is avariantsconnection. And all that a connection means in GraphQL is a list ofedges. Anedgeis a relationship between twonodes. At the end of thatedgeis thenodethat has the fields in question that I want to get here.

Theseproduct nodesare related tovariant nodes. The line represents anedgeand thosevariantsin turn have titles on them. So any time you see the word connections, just keep in mind you'll need to think ofedgesandnodesto abide by this underlying model for the API of what's actually going on.

You might also like:Developing Your First Shopify App: 4 Mistakes to Avoid.

Writing data with mutations in GraphQL

我们查询数据覆盖。这是th的一半e equation. We also want to be able to write data.

得到started with graphql: giff of writing data in the form of mutations

This was a query, implicitly, if you leave out the wordqueryhere right at the top, it's a query. We actually need to write the wordmutation.

If I back up through the schema all the way to the root, I canqueryand I can makemutations. Let's investigate what types ofmutations I can make.mutations, as we covered, cover everything for writing data. That's creating, canceling, updating, deleting.

我们的名字mutations in Shopify is we talk about the thing that themutationis being made on to start and then the actual operation that's taking place there. Without scrolling alphabetically through this, I'm going to type in the letterP. It knows and you can see there'sproductCreate,productDelete, andproductUpdate. So, I'm going to sayproductCreate.

I can scroll through this schema on the right hand side alphabetically or I can hover over this with my mouse and then just click on this name down here, and it will quick-jump you to the definition of thismutation, which is very handy.

Now, what's going on here? This is saying that when you make thismutation, you'll get aProductCreatePayloadback. There are a couple of arguments that are supplied as part of thismutation. If you're familiar with the REST API, when you make aproduct,response is all the information you could ever want about that newly created product.

Just like with queries, I can specify only the data that I want back. I have to sayproductfirst and then the stuff you'd expect, created at,description,title, and so forth. More important here are the arguments. This is saying that this product input type is not nullable by the exclamation point. So, I have to provide that.mediais an array of this non-nullableCreateMediaInputtype. However, the array itself is not non-nullable, so I don't have to provide that. In other words, all I need to provide is thisProductInput.

As with all arguments, I'm going to open a set of parentheses. I'm going to format this as an object. I'm also going to create atitleon this product called"Awesome Product". I'm going to open up thatmutationto receive theProductCreatePayload, which has a field on it calledproduct. Inside of thatproductis where I can get all the good stuff. I'm going to say I want theIDand then just to make sure, I'm going to say I want thetitle. I'll make thismutationby pressingplayand you'll notice theIDif you've worked with the REST API before.

得到started with graphql: giff of writing data with mutations, final

This is a little bit different, theIDs are not interchangeable. GraphQL has its own set ofIDs to refer tonodes. Don't try to use theIDs interchangeably and indeed, I get"title": "Awesome Product"back.

将继续GraphQL

Congratulations! That's getting up and running with making both queries and mutations on a Shopify store with GraphiQL. So, where do you go from here to expand your knowledge set on GraphQL?

There are many fantastic resources out there on the web for learning about GraphQL. Some of them are published by Shopify and I've called out through this tutorial, notably our tutorial andmaking your first GraphQL request. This is an excellent resource to come back to, especially after watching this tutorial. The mutations and queries that you'll make in that tutorial are a little bit different, but the principles are the same.

"There are many fantastic resources out there on the web for learning about GraphQL."

Shopify'sGraphQL learning kitis a great place to start if you want to get up and running with a standalone HTTP client. As part of that blog post, there's a discussion of GraphQL as well as a downloadable set of preconfigured insomnia queries and mutations to start working with against a store. You'll just have to populate a private app key.

If you're interested in the storefront API SDKs and how they can make working with the storefront API even faster. You can check out ourStorefront API Tools Library.

I cannot understate how awesome the “how to GraphQL” set of tutorials are. They go much deeper than this particular tutorial does and there are great practical tutorials for working with certain frameworks, libraries, and languages. For example, if you're a React developer and you want to understand React and Apollo together, a very popular client library, there's a step-by-step tutorial on exactly how to do that.

It also never hurts to go to theofficial GraphQL site, which really lays out in very clear language the specification of GraphQL itself, a very helpful resource for moving forward.

Grow your business with the Shopify Partner Program

Learn more