Skip to content

Getting Started with the Aqueduct Framework – From Beginner to Intermediate

Posted in Dart

You ain’t got time. I ain’t got time neither. Let’s get over with this pretty quick. Here are some of the facets of the Aqueduct framework you stand to understand in this guide.

  • Starting an Aqueduct project
  • Handling Migrations and providing seed data or fixtures for migrations
  • Routing i.e handling params and query params
  • Making CRUD requests, handled by a controller
  • Basic pagination in Aqueduct

The reason behind the article (as well as many of my others) is the frustration I went through getting the most basic things done, and the unnecessary lengthy process other tutorials/articles go through, winding long and time wasting. For the full rant, see at the end.


Without much ado, let’s rock and roll

Setting Up Project

I’m not gonna pamper you. Here’s how to setup an Aqueduct project. You don’t have time, so just straight to the meat

Setting up Database

Create a postgreSQL database, call it anything you want.

Then create a database.yaml file in the root project (same location as the pubspec.yaml), and put in these:

The above database.yaml is for the aqueduct dbcommands to make connections to the DB. However, for our application to speak to the database, we need update the config.yaml with our database info.


If you’re coming from somewhere like Django or Laravel, migration is likely a piece of cake for you.

Otherwise, think of migrations as a way to communicate your schema changes to the database, for real. If you didn’t get that, please google for more details on migrations


Last but not least, run the migration, to convey what database scheme we have and want to the database for real. You can check the migrations/00000001_initial.migration.dart file to see what is gonna happen.

Also, seed/fixture data is added for free. Duh!

Run aqueduct db upgrade to commit the migration to database.

Run Application

In the project root folder, run aqueduct serve

Since this project is API endpoints only, you’ll have to consume using a REST Client, such as Postman.

If using Postman, check out this API request collection to help you get started with consuming your endpoints. You might wanna read on how to use the collection in Postman.

We’ve spent 2 seconds already. That’s too much time. Let’s actually get into the real deals

Application Channel, Entry Point & Routing

In the lib/channel.dart is where we find, more or less, the bootstrapping of our application, and the few get-outta-ways we’ll want done, such as, keeping in touch with the database by loading the config file, and also create one or two routes.

Nothing extraordinary happening. Just the same principles you know from your Ruby, DJango or Laravel. Heck, even Angular!

Update your lib/channel.dart file with these contents

In English:

  • We’re importing a few. dart:io allows us to read files (we’re reading the config.yaml file. Think of it as magic!
  • We also load a controller file. If coming from Angular, think of it as the Component.ts file
  • Then we create some routes. This part, lemme explain a bit

router.route('/icd/[:id]').link(() => ICDController(context));

A lot is packed into the above single line of code. So much, I can write a book on this line alone. But well…

The above route matches anything like this

The [:id] is the params. The [ ] around it makes that param optional. Thus, without an ID param, it’ll do just fine. So if comes in, the application will do just fine.

You’ll see how each of these HTTP verbs are handled in the controller when we switch over to it next. However, know that we’re using the HTTP verbs, as many as possible, at least to distinguish the requests

The ICDController() was imported from the controller, which handles the routes coming through this endpoint.

To pass query params, just pass it in the URL, like it’s done in other frameworks. You only deal with the query params, if any, when in the controller.

So you see, just a single line, all the punch it throws? Excited yet? If not, the Controller might whet you up!

Mr. Controller

Our Component Controller is where we handle the logic and communicates through the ORM that comes with Aqueduct to the Database. Above, we setup to use PostgreSQL, which depending on who you ask, is the best Relational Database platform in the world.

‘Abeg’, let’s end the DB wars here!

Like other platforms, having a model for our database means we can more or less regulate what data structures flows through our application, and into the database.

In Aqueduct, we do so, by creating a model/icd_model.dart

The only part I’ll comment on is

In Mongoose (ExpressJS, NodeJS, MongoDB), there’s the { timestamp: true } option, which when specified, any data passing through the schema into the database, gets a createdAt and updatedAt timestamps. It happens for free, seamlessly

In Aqueduct, a similar result can be achieved via the model signals. In the case above, whenever there’s an insert about to happen – willInsert(), cook up a createdAt field, slap in there the current Datetime in UTC format, then add as a field to whatever data structure is about to be saved.

The rest of the code is, well, code!

I keep dangling the controller in your face, but not showing you the code? Here we go

A lot to unpack here? Nope. Just a few

@Operation.get(), etc, is more like the router.get('/url', req, res) in Express

Take note of this part first,
getAll({@Bind.query('limit') int limit, @Bind.query('offset') int pageby})

When you do getAll(@Bind.query...) it means totally different from getAll({@Bind.query ...})

Do you notice the difference? The second one is in curly braces. The first is not.

Within curly braces mean it’s optional parameters, thus, in the above within the braces, if the URL doesn’t supply any query params, we’ll do just fine.

limit ??= 100; is basically saying if the limit query param is empty, just assign 100 to that var. Same for the pageby

The rest, is just straight code written in English. If any part of the controller code isn’t clear, kindly leave a comment. Otherwise I take it as y’all readers (more like my classroom students) said “Yes sir, we understand” to me.


And so we’re done. Source code to above project here:

Aqueduct CRUD API

Leave any comments you might have in the comment section below.

If you’re into tests, check the sample Test I wrote for this project.


So trying to get information about getting up and running with Aqueduct has been a challenge. First, it paints a picture of how relatively young the project/framework is.

Unfortunately, all the tutorials I could come across are all either outdated or unnecessarily lengthy for no reason.

Lengthy preambles, long explanations are all, for the most part, not useful. Like myself, I believe many are familiar with a few frameworks already, and know what exactly they want to get things done.

They know they need routing, components, a data model, and likely an ORM to communicate with the DB.

Also, it’s pretty easy installing frameworks these days, plus usually it’s the clearest instructions on a framework’s page. So tutorials online going over again installation steps for frameworks don’t help that much.

I’ve fallen victim to such writing schemes in the past. Those are helpful for absolute beginners, but as you can see from the above article, it’s targeted to developers who are a lot familiar with the drillI