Dart Frog — A minimalistic backend framework for Dart

Razvan Tamazlicariu
6 min readAug 12, 2022

--

Dart Frog is an experimental framework created by Very Good Venture. This is a fantastic tool, helping developers to have a unified tech stack and share models, logic, and other parts of the application.

Other tutorials on Dart Frog:

Because this project is still under development, I don’t recommend it to be used in production.

Supported features:

  1. Hot Reload ⚡️
  2. Dart Dev Tools ⚙️
  3. File System Routing 🚏
  4. Index Routes 🗂
  5. Nested Routes 🪆
  6. Dynamic Routes 🌓
  7. Middleware 🍔
  8. Dependency Injection 💉
  9. Production Builds 👷‍♂️
  10. Docker 🐳
  11. Static File Support

As for now, these are the only features available. Support for web sockets or things like protobuff support, web rtc, and authentication are not available at the moment. You can check the roadmap here.

Source code:

You can skip. ahead and download the source code HERE

https://github.com/razvantmzz/medium_articles/tree/dart_frog_demo

Getting Started

The easiest way to start is by installing the dart_frog_cli. To install the CLI, open the terminal and type the following command:

# Install from pub.devdart pub global activate dart_frog_cli

At this point, dart_frog should be available. You can verify by running dart_frog in your terminal.

Create a project

Simply run this command. This will create a folder with everything you need inside.

dart_frog create name_of_your_project

Start dev server:

By default, dev server will start on port 8080.

# Start dev server
dart_frog dev
# Server is now running at: http://localhost:8080/

Create a Production Build

Create a production build that includes a DockerFile so that you can deploy anywhere:

# Create a production build
dart_frog build

Creating a new Route

In dart_frog a route is represented by an onRequest function, also called a route handler, exported from a dart file in the routes directory. Each file represents an endpoint, named after the file. Files named index.dart will correspond to a / endpoint

For example, if you create a file in routes/username/index.dart that exports an onRequest method, you can access the endpoint via /username.

To create this route create a folder username in routes.Inside routes/username create an index.html file and paste this:

import 'package:dart_frog/dart_frog.dart';Response onRequest(RequestContext context) {
return Response(body: '[{"username": "Alex"}]');
}

All route handlers have access to a RequestContext which can be used to access the incoming request as well as dependencies provided to the request context.

OnRequest will get called for each HTTP request method. In order to differentiate between GET or POST you have to manually check the type:

switch (method) {
case HttpMethod.get:
// TODO: GET method
break;
case HttpMethod.post:
// TODO: POST method
break;
case HttpMethod.put:
// TODO: PUT method
break;
case HttpMethod.delete:
// TODO: DELETE method
break;
}

Synchronous vs Asynchronous

By default, route handlers are synchronous. To create an asynchronous route handler, we need to add the keyword async and return a Future<Response>.

Future<Response> onRequest(RequestContext context) async {
return Response(body: '[{"username": "Alex"}]');
}

Returning a response

OnRequest function returns a Response. We can customize the status code of the response via the statusCode parameter. We can also return a JSON response via the Response.json constructor.

Response onRequest(RequestContext context) {
return Response.json(
statusCode: 404,
body: <String, dynamic>{'hello': 'world!'},
);
}

Dynamic routing

Dynamic routing in dart_frog is a little strange. If we want an endpoint, that returns the username given an id, we would access an URL that looks like this: /username/1 , where 1 is the id. In dart_frog we know that each route is represented by a file. In the username folder create a file named [id].dart .

Routing parameters are forwarded to the onRequest method as below:

Response onRequest(RequestContext context, String id) {
return Response.json(body: <String, dynamic>{'id': id});
}

Middleware

Middleware allows you to execute code before and after a request is processed. You can modify the inbound and outbound responses, provide dependency injections, log the request, etc. This is useful for validating authorization tokens, adding common headers, logging, etc.

Just like routes, middleware consists of 2 parts: a file, with a specific place and name, and a middleware function. The file needs to be placed within a subdirectory of the routes folder and there can be only 1 middleware file in the same subdirectory.

The middleware is executed for all routes in the same subdirectory. For example, placing a middleware file in /routes will get executed for all endpoints. Placing it in the /routes/username directory will get executed for the routes under the /username directory.

To create a middleware, add a file named _middleware.dart and paste the middleware handler:

import 'package:dart_frog/dart_frog.dart';

Handler middleware(Handler handler) {
return (context) async {
// Execute code before request is handled.

// Forward the request to the respective handler.
final response = await handler(context);

// Execute code after request is handled.

// Return a response.
return response;
};
}

For logging request we have a build in middleware called requestLogger() .

We can chain middleware via the use extension. Chained middleware are executed in the order they are added before the request is executed, and in the inverted order after the request is executed.

A -> B -> C -> execute request -> C -> B -> A

import 'package:dart_frog/dart_frog.dart';Handler middleware(Handler handler) {
return handler
.use(requestLogger())
.use(mainHandler());
}
Middleware mainHandler() {
return (handler) {
return (context) async {
// Execute code before request is handled.
// Forward the request to the respective
handler.print("methods type before ${context.request.method}");
final response = await handler(context);
print("methods type after ${context.request.method}");
// Execute code after request is handled.
// Return a response.
return response;
};
};
}

Be careful to chain the .use().

// DON'T
return handler
..use(requestLogger())
..use(mainHandler());
// OR
handler.use(requestLogger())
handler.use(mainHandler());
return handler;
//CORRECT WAY
return handler.use(requestLogger()).use(mainHandler());

Dependency injection

Middleware can also be used to provide dependencies to a RequestContext with the help of a provider.

Provider is a different type of middleware. It can create and provide an instance of a class or type T to the RequestContext . The create callback is called lazily.

I created a class called Username in lib/usernames.dartwhich returns a random name.

///Holder class for returning a random username
class Usernames {
final List<String> _usernames = [
'Andrei91',
'Rabit123',
'Codingcamp44',
];
late final _random = Random();///Returns a random name
String getName() {
final index = _random.nextInt(_usernames.length);
return _usernames.elementAt(index);
}
}

Next, in /routes/username we create a new middleware. We want to inject the Username class, just for routes under username.

We’ll use provider to inject the Username() instance into our request context.

import 'package:dart_frog/dart_frog.dart';
import 'package:frog_playground/usernames.dart';
Handler middleware(Handler handler) {
return handler.use(provider<Usernames>((context) => Usernames()));
}

Later, Usernames can be accessed from within a route handler using context.read<T>():

import 'package:dart_frog/dart_frog.dart';
import 'package:frog_playground/usernames.dart';
Response onRequest(RequestContext context) {
final username = context.read<Usernames>();
return Response.json(
body: <String, dynamic>{'username': username.getName()},
);
}

Debug dart_frog in VS Code:

If you want to debug in VS Code instead of Debug Dart Tools, follow these steps:

  1. run dart_frog dev inside VsCode terminal
  2. copy DevTools Debugger Url. should look like this:

http://127.0.0.1:8181/nYPpoWRg8g8=/devtools/#/?uri=ws%3A%2F%2F127.0.0.1%3A8181%2FnYPpoWRg8g8%3D%2Fws

3. Delete “/#” after devtools from the URL. It should look like this:

http://127.0.0.1:8181/nYPpoWRg8g8=/devtools/?uri=ws%3A%2F%2F127.0.0.1%3A8181%2FnYPpoWRg8g8%3D%2Fws

4. Press Command + P, on Mac( opens navigate to any file window) and write: > Debug: Attach to Dart Process hit enter and paste the Url from step 3.

That's it.

I will follow up soon with more tutorials on Dart Frog, like connecting to a database and deploying. Make sure to subscribe :D.

Connect Dart Frog to a database tutorial:

Liked the article? Let me know with👏👏👏

https://www.buymeacoffee.com/razvantmz
buymeacoffee.com/razvantmz

Couldn’t find a topic of interest? Please leave comments about topics you would like me to write!

Follow me at Medium!

References:

https://verygood.ventures/blog/dart-frog

https://dartfrog.vgv.dev/docs/overview

--

--

Razvan Tamazlicariu

Android & Flutter Developer 📲 Contractor ✅ Freelencer