This is the first blog of a series of articles where we’ll talk about how to build Full Stack apps using only Dart. If you are a Flutter developer, you have certainly been using Dart for a long time. However, what happens when you have to integrate with a backend?
Normally, backends are made with a different programming language, yet nowadays, Dart has become more popular in the backend because there are lots of options to choose from. Flutter devs who are already using Dart are going to benefit greatly from writing their backends in Dart. So today we will focus on Dart Frog.
What is Dart Frog?
Well, as shown in their docs, Dart frog is a “fast minimalistic backend framework for Dart”. It’s basically a higher level abstraction on top of shelf, written by the open-source team at Very Good Ventures. If you want to know more about Dart Frog, you can check out their docs or visit this recewornt podcast episode where we talk about it!
Building a Full Stack App with Dart Frog
First, let’s discuss what we are building today. The purpose will be to build an app in Flutter that can retrieve a list of users from an API. With this simple example, we will be able to:
- Create our own Rest API using Dart Frog.
- Create a Flutter app and connect it with the API using an HTTP client.
- Discuss the advantages of having Full Stack apps using only Dart.
At the end of the article, we will have something like this:
Before we jump into the example, we have to say there are a couple of disclaimers:
- There are different technologies on how to build your API being the most popular ones: Rest API, GraphQL, web sockets and gRPC. This will ultimately depend on the problem we want to solve, but in this example we are going to go for a Rest API. Hopefully we will continue this series with other types of backend technologies.
- We’ll be building a minimal example of a Full Stack app, so we might not use all the best practices that are suggested.
Building a Rest API with Dart Frog
First, we need to install Dart Frog and create our server folder. We are going to have a folder structure mainly with two different folders, one for the backend called server and the other for the frontend called client.
In our server folder, run the command:
Now, the structure of a Dart Frog project is very bare-bones. If we are familiar with dart packages we can add any dependency that we want such as json_serializable, equatable and so on.
If we go to the routes folder, this is where all the magic will happen. We want to define our endpoints for having basic CRUD operations on our users collection. So, there is a little boilerplate needed to create these endpoints, I recommend using this brick that facilitates that process. If you don’t know what bricks and mason are, I recommend reading this article called “Enhance your Flutter development with mason” where we talk about it.
Let’s enter to our routes and create two nested folder called api and v1. This will let us have all of our endpoints under api/v1 folder. Then run the mason make command with our brick:
Then we simply input users as the name of our collection of endpoints.
We can see now that we have 5 different endpoints.
If we follow the REST standards, we know we need to have the following notation:
- GET /users
- GET By id /users/:id
- POST /users
- PUT /users/:id
- DELETE /users/:id
On one hand, we have a file called index.dart inside routes/api/v1/users that contains both endpoints for getting all users and creating a new user.
On the other hand, we have a file called [id].dart. This is the actual notation for endpoints that received a dynamic parameter, in this case an ID. Inside this file we can see all the endpoints that need the ID parameter such as: updating a user, deleting a user or simply getting a single user by the ID.
If we run the server, we will be able to hit those endpoints, so let’s try it!
The beauty of creating the backend using Dart Frog is that we can now treat the project as if it were a regular pure Dart package. That’s why we are going to create our src folder and inside that a models folder, where we will create our user model.
But first, let’s add some dependencies to our pubspec.yaml:
Then create a user.dart file inside src/models with the following content:
Now we can auto generate our user model with:
For practicality, let’s also create a barrel file to import all the models called models.dart and export our user model file:
Next we will need to create a data source. Whenever a route will be executed, it has to get the data from somewhere, right? That’s why we’ll introduce to our example a generic interface for a data source, so no matter if we have an in memory strategy or a database, routes will communicate using an interface.
And now for the implementation, we can use a simple in memory strategy. Notice that we added some hardcoded users for testing purposes.
The last step required is to inject the data source using a middleware. Middleware is no other than a function that is executed before or after a request is processed. That’s why it seems to be the perfect place to put our data source, so we have to make sure every route will have access to it. There are other use cases such as handling user authentication, yet we will keep it simple for this example.
So now, after all the heavy implementation of models, data sources and middlewares we can go back to where we started and finally finish our routes methods. Let’s keep only the get all users and get user by ID routes as we are not going to cover create, update and delete a user.
If we go to our get route, we have to call our data source in order to get the list of available users. Then we simply return the list of users. In this way, the route doesn’t depend anymore on where the data is coming, resulting on a more testable route where we can mock this dependency work with a local implementation in development or a production database when it is deployed.
In the same way, we have to modify the get by ID route, so it can call the in memory data source and try to retrieve the user.
At this point we are pretty much finished with the Dart Frog implementation as every route is correctly setup. However, we know our Flutter app will need a client that knows how to interact with the provided routes so we are going to go a step further and create that HTTP client as part of our backend project. Having an HTTP client is something that would maybe be out of the box in future releases of Dart Frog – for this, check out the roadmap. This could potentially be written as part of our frontend packages as well. However, as we are going to make a repository package in the Flutter app, we decided to leave the client in the Dart Frog project.
First, let’s add a new dependency:
Now let’s make our HTTP client that can hit the URLs we made:
For the last step, we simply have to export everything needed as part of our library so that our app in Flutter can actually use it.
We need to create a file inside lib that defines the name of the library and export all the necessary files:
Building the frontend with Flutter
Now that we have our small Rest API in place, we are going to connect it using Flutter.
First, let’s create our project in the client folder:
Let’s import the API into our Flutter project, so we can use it in our pubspec.yaml file:
We will also need to build a repository that uses the API Client and basically acts as a middleware. It will parse the Dart Frog responses as well as throwing errors, if any.For that, we will need to create a pure Dart package inside our Flutter app, so we can create a folder at the same level of lib and call it packages.
Then we can create our pure Dart package using:
Under our lib folder we only need to create a UserRepository class with the following:
Next, let’s inject our dependencies, our User Repository and API client into a RepositoryProvider. In that way, we will be able to access to that instance from anywhere using the BuildContext. We’ll also create a bootstrap method, so we can initialize all the dependencies before launching the app.
bootstrap.dart:
For our main feature, we are going to display a list of users. To create the feature using best practices, we are going to need a bloc folder and a view folder. There’s some boilerplate required here, so we are going to use another brick called feature_route_bloc that we can download from BrickHub.
Once we named the feature users, mason will generate all the necessary folder structure with our BLoC and view folder and barrel files.
In our users_event.dart file, we will only define an event for fetching all the users from the dart frog API:
In our users_state.dart we will add a property for the list of users, so we can get the following state:
Finally, we can add to our BLoC our new event and its handler, resulting on the following:
Then in our view we just need to create the list of users and connect it to our BLoC.
User View
User List Widget
Well, know everything should be up and running, let’s run both the backend and frontend to see everything in action.
To run Dart Frog locally and in Flutter in a simulator or any device we can use:
That’s it! We did it! Everything is up and running.
Final thoughts on Flutter and Dart Frog for building Full Stack apps
After building our whole example, we can now say:
Sharing code
Building the backend with Dart can allow you to share a lot of code between the frontend and the backend. This can really improve efficiency between teams (if your team is divided in backend and frontend) because now every change in the backend will be spread to the frontend immediately.
Let’s say we change a property in the user model in the backend, the frontend will now update that change as it’s using the same models, and we won’t be having undesired parse errors when we run the frontend while calling the API.
Dev experience
Because we are using the same language, apart from reusing a lot of the code, libraries, standards e.g: linters, we can also be benefit from having the same seamless experience of the available tooling.
Using Dart could be a real benefit, since we don't have to switch contexts to a different language with different notation and practices. By sharing the same practices around Dart, your team can focus on just delivering features, sharing as much as possible, and unifying the team.
Should we create backends with Flutter and Dart Frog?
Yes, if your team feels comfortable enough and has experience with the Dart programming language, Dart Frog offers a really low entrance barrier to start developing your backends in Dart.
Furthermore, your team will experience all the benefits mentioned above. I suggest to start with a small project and try to gain experience identifying the best practices as well as working with the same language and try to make the best out of it.
One drawback could be that using Dart for the backend is a very recent practice, and other languages/frameworks might have more libraries and resources available, yet Dart is gaining popularity and momentum, so it’s not risky at all.
If you like the article, tell us if you want to see more about backend technologies using Dart and combining it with Flutter!