Listing With Get and Query Parameters
00:00
In the previous lesson, I showed you the beginnings of our library API, starting with the ability to fetch a single book by ID. In this lesson I’ll show you the other use of GET for fetching a list of books.
00:12 I’ll also show you how to use query parameters to modify what comes back from the query. When browsing the web, you may have noticed the use of a question mark in the URL.
00:22 This is a way of passing parameters to the server. After the question mark, you get one or more key-value pairs indicating a variable name and its contents, and sometimes there are multiple parameters.
00:34 You do that by separating the key-value pairs with ampersands. HTTP is picky about what characters can be in a URL, for example, forward slashes, question marks, equal signs, and ampersands are meaningful.
00:47 So if your variables need to contain these kinds of characters, you have to escape the URL first.
00:53
Python’s urllib module in the standard library has tools that help you do this. When you write a FastAPI function, any function arguments that aren’t explicitly named in the URL template get passed into your function as query parameters.
01:08 So you need very little code to take advantage of this feature.
01:12 Speaking of templatized arguments, a quick note on when to use those versus when to use query parameters. Functionally, you can achieve the same thing with both techniques, but best practice is for anything that is mandatory to be a templatized argument, while things that are optional are query parameters.
01:30 The book ID in the previous lesson needs to be there for the fetch to work, so it was a templatized argument. In this lesson, I’m going to use query parameters to optionally adjust what comes back from a query.
01:41
And since it is optional, using query parameters is the better choice. Let’s go add some code to our library API. I’m back in main.py in the books/ directory.
01:54
What is on the screen here is the new function that I’ve added for this lesson. Even though I’m after a list this time, I’m still using a GET.
02:01
Whether you get a single thing or a list, you’re using the read part of CRUD in the REST API, which always means the HTTP GET method. The get-list URL is usually different from the get-item URL, typically being the plural books with an s instead of book.
02:19 You can do fancy things so that they’re the same, but different is clearer to your users. Note the lack of brace brackets in this URL. There’s only one version of it, and that’s the fetch-everything, sort of.
02:30 I’ll get back to that in a second. The function signature has two arguments since there were no brace brackets in the URL, FastAPI treats these function arguments as query parameters.
02:41
The first parameter is limit. I’ll be using it to limit the number of items that get returned in the list.
02:48
This is a common feature in API listing endpoints, and the limit frequently defaults to a value like 50, so that you’re not running super massive queries on the backend.
02:58
Like with templatized arguments, you use a type hint so FastAPI does the conversion for you. I need my limit to be an integer, so here I’m setting that, and I’m also setting a default value of None.
03:11
The second parameter is similar. limit limits how many items come back. Say you limit the query to 50. Well, how do you get the 51st item? By using an offset.
03:22
If offset is present, then the query starts at that value. Sometimes this same kind of mechanism is done through a page number and items-per-page parameter instead. It achieves the same end.
03:33
Now I need a bit of code to handle the logic of limits and offsets. First, I check if both the limit and the offset are set to something.
03:42
If so, I run the .values() method on the dictionary that returns an interval of books. Since Python 3.7, dictionaries are ordered based on item insertion, so the books will be in the order of their IDs.
03:55
In a more complex system, you also might have to sort here, for example, using an ORDER BY clause in your SQL.
04:03
In order to limit the stuff that comes back, I have to convert it to a list first. For some reason, the special iterable that comes back from .values() isn’t subscriptable.
04:13 I’m not sure why. I would’ve expected it to be able to use square brackets, but well, that’s how it goes. So once I’ve got it as a list, I then return the subset starting at the offset point, and proceeding for a limit number of items after that.
04:29
As a little aside, this is not a particularly efficient way of doing things. The .values() call returns all items before producing the subset.
04:37 If you were dealing with a database, you would not want the whole table to return before you chopped it. Seeing as our data is small and in memory, this isn’t a big deal, but if you were doing this in the real world, you would want to be conscious of just how this query worked.
04:51
For example, in SQL, you can pass a limit and offset control to the query and let the database do the chopping for you. This is actually why limit and offset are more common than page and items per page.
05:03
With the latter, you have to do some extra math. With limit and offset, you can just pass it straight to the database. Truly lazy programmers, be lazy.
05:11
This second case is for when the offset isn’t set, but the limit is. So same idea as before, just starting at the beginning of the list.
05:20
I actually could have set the default value of offset to 0 and skipped this, but I didn’t really think of that until now. So well, what are you going to do?
05:29
And this is the default clause, returning all the books in the library. While I’m critiquing this code on the fly, it also doesn’t handle the offset-only case. In the real world, you’d probably also want to put some boundary checks on limit so that the user doesn’t set the limit to 1_000_000, which would kind of defeat the purpose of limits in the first place.
05:48 Alright, critiques aside, let’s try this out.
05:59 Let me just scroll back here.
06:01
The URL I hit was the default, using no query parameters, and it returned a list containing our two books. If you’re wondering what the other stuff on the end is, in Unix, you can send the output of one command to the input of another with the pipe operator. curl runs the query and gets back our data.
06:19
You’ll recall from previous lessons that the result is all on the same line and has no spaces in it, making it a little hard to read. The Python standard library’s json module can be run as a program through the -m option, and it pretty-prints JSON to the screen, making our books easier to read.
06:37
Note that before Python 3.14, you would have to use python -m json.tool instead. The shorter version was a recent addition. Also, when you combine curl with a pipe, it tends to print out extra information that you don’t want on the screen.
06:53
The -s argument I used there basically just tells it not to do that. Let me scroll back down and I’ll try it again, this time with a query parameter.
07:10
With the limit set to 1, I get back only the first book. Notice, I put quotes around the URL this time. I wasn’t a hundred percent sure how my shell would deal with things like question marks, so this was just the safer way to do it.
07:30
And with the limit set to 1 and the offset set to 1, I get the second book. Limits and offsets aren’t particularly helpful when you’ve only got two books, but hopefully you get how the idea works.
07:43 Two books isn’t exciting. So in the next lesson I’ll add the create part of CRUD along with updating and deleting.
Christopher Trudeau RP Team on Feb. 28, 2026
Hi James,
Yep, using json on its own is a Python 3.14 addition. Before that you have to use json.tool.
Become a Member to join the conversation.

James Butler on Feb. 27, 2026
I had to use json.tool to get it to work on my Zorin OS 18 machine.