Skip to content

VeryGoodOpenSource/dart_frog

Folders and files

NameName
Last commit message
Last commit date

Latest commit

d961ec5 Β· May 29, 2022
May 24, 2022
May 4, 2022
May 26, 2022
May 25, 2022
May 29, 2022
May 24, 2022
May 25, 2022
May 24, 2022
May 25, 2022
May 27, 2022
May 26, 2022
May 2, 2022

Repository files navigation

Dart Frog Logo Dart Frog Logo

ci coverage pub package style: very good analysis License: MIT Powered by Mason

A fast, minimalistic backend framework for Dart 🎯

Developed with πŸ’™ by Very Good Ventures πŸ¦„

Experimental 🚧

Dart Frog is an experimental project under development and should not be used in production at this time.

Quick Start πŸš€

Prerequisites πŸ“

In order to use Dart Frog you must have the Dart SDK installed on your machine.

Installing πŸ§‘β€πŸ’»

# πŸ“¦ Install the dart_frog cli from pub.dev
dart pub global activate dart_frog_cli

Creating a Project ✨

Use the dart_frog create command to create a new project.

# πŸš€ Create a new project called "my_project"
dart_frog create my_project

Start the Dev Server 🏁

Next, open the newly created project and start the dev server via:

# 🏁 Start the dev server
dart_frog dev

Create a Production Build πŸ“¦

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

# πŸ“¦ Create a production build
dart_frog build

Goals 🎯

Dart Frog is built on top of shelf and mason and is inspired by many tools including remix.run, next.js, and express.js.

The goal of Dart Frog is to help developers effectively build backends in Dart. Currently, Dart Frog is focused on optimizing the process of building backends which aggregate, compose, and normalize data from multiple sources.

Dart Frog provides a simple core with a small API surface area in order to reduce the learning curve and ramp-up time for developers. In addition, Dart Frog is intended to help Flutter/Dart developers maximize their productivity by having a unified tech stack that enables sharing tooling, models, and more!

Feature Set ✨

βœ… Hot Reload ⚑️

βœ… Dart Dev Tools βš™οΈ

βœ… File System Routing 🚏

βœ… Index Routes πŸ—‚

βœ… Nested Routes πŸͺ†

βœ… Dynamic Routes πŸŒ“

βœ… Middleware πŸ”

βœ… Dependency Injection πŸ’‰

βœ… Production Builds πŸ‘·β€β™‚οΈ

βœ… Docker 🐳

🚧 Generated Dart Client Package πŸ“¦

🚧 Generated API Documentation πŸ“”

Documentation πŸ“

Routes 🚏

In Dart Frog, a route consists of an onRequest function (called a route handler) exported from a .dart file in the routes directory. Each endpoint is associated with a routes file based on its file name. Files named, index.dart will correspond to a / endpoint.

For example, if you create routes/hello.dart that exports an onRequest method like below, it will be accessible at /hello.

import 'package:dart_frog/dart_frog.dart';

Response onRequest(RequestContext context) {
  return Response(body: 'Hello World');
}

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 (see middleware).

import 'package:dart_frog/dart_frog.dart';

Response onRequest(RequestContext context) {
  // Access the incoming request.
  final request = context.request;

  // Return a response.
  return Response(body: 'Hello World');
}

We can customize the status code of the response via the statusCode parameter on the Response object:

import 'package:dart_frog/dart_frog.dart';

Response onRequest(RequestContext context) {
  return Response(statusCode: 204);
}

In addition, we can return JSON via the Response.json constructor:

import 'package:dart_frog/dart_frog.dart';

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

Route handlers can be synchronous or asynchronous. To convert the above route handlers to async, we just need to update the return type from Response to Future<Response>. We can add the async keyword in order to await futures within our handler before returning a Response.

import 'package:dart_frog/dart_frog.dart';

Future<Response> onRequest(RequestContext context) async {
  final result = await _someFuture();
  return Response(body: 'Result is: $result!');
}

Dynamic Routes πŸŒ“

Dart Frog supports dynamic routes. For example, if you create a file called routes/posts/[id].dart, then it will be accessible at /posts/1, /posts/2, etc.

Routing parameters are forwarded to the onRequest method as seen below.

import 'package:dart_frog/dart_frog.dart';

Response onRequest(RequestContext context, String id) {
  return Response(body: 'post id: $id');
}

Middleware πŸ”

Middleware in Dart Frog allows you to execute code before and after a request is processed. You can modify the inbound request and outbound responses, provide dependencies, and more!

In Dart Frog, a piece of middleware consists of a middleware function exported from a _middleware.dart file within a subdirectory of the routes folder. There can only ever be once piece of middleware per route directory with routes/_middleware.dart being middleware that is executed for all inbound requests.

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;
  };
}

We can chain built-in middleware, such as the requestLogger middleware via the use API. For example, if we create routes/_middleware.dart with the following contents, we will automatically log all requests to our server.

import 'package:dart_frog/dart_frog.dart';

Handler middleware(Handler handler) {
  return handler.use(requestLogger());
}

Dependency Injection πŸ’‰

Middleware can also be used to provide dependencies to a RequestContext via a provider.

provider is a type of middleware that can create and provide an instance of type T to the request context. The create callback is called lazily and the injected RequestContext can be used to perform additional lookups to access values provided upstream.

In the following example, we'll use a provider to inject a String into our request context.

import 'package:dart_frog/dart_frog.dart';

Handler middleware(Handler handler) {
  return handler
      .use(requestLogger())
      .use(provider<String>((context) => 'Welcome to Dart Frog!'));
}

We can later access the provided via from within a route handler using context.read<T>():

import 'package:dart_frog/dart_frog.dart';

Response onRequest(RequestContext context) {
  final greeting = context.read<String>();
  return Response(body: greeting);
}

Testing πŸ§ͺ

In Dart Frog, we can unit test our route handlers and middleware effectively because they are plain functions.

For example, we can test our route handler above using package:test:

import 'dart:io';

import 'package:dart_frog/dart_frog.dart';
import 'package:mocktail/mocktail.dart';
import 'package:test/test.dart';

import '../../routes/index.dart' as route;

class _MockRequestContext extends Mock implements RequestContext {}

void main() {
  group('GET /', () {
    test('responds with a 200 and greeting.', () async {
      const greeting = 'Hello World!';
      final context = _MockRequestContext();
      when(() => context.read<String>()).thenReturn(greeting);
      final response = route.onRequest(context);
      expect(response.statusCode, equals(HttpStatus.ok));
      expect(response.body(), completion(equals(greeting)));
    });
  });
}

In the above test, we're using package:mocktail to create a mock RequestContext and stub the return value when calling context.read<String>(). Then, all we need to do is call onRequest with the mocked context and we can assert that the response is what we expect. In this case, we're checking the statusCode and response body to ensure that the response is a 200 with the provided greeting.

Additional Resources πŸ“š

For more information, see the example and our roadmap.

πŸ’‘ Fun Fact: the dart2js compiler used to be called frog.