Running a Finch Application

A Finch application needs two files: app.dart (application setup) and an entry point — typically bin/main.dart for production or lib/serve.dart for development with file watching.

app.dart — Application Setup

app.dart creates the FinchApp instance, registers routes, and defines main(). This is the canonical structure based on the Finch example project:

// lib/app.dart
import 'package:finch/finch_app.dart';
import 'package:finch/finch_tools.dart';
import 'package:finch/finch_route.dart';
import 'package:finch/finch_console.dart';
import 'route/web_route.dart';

FinchConfigs configs = FinchConfigs(
  port: env.getInt('DOMAIN_PORT', 8080),
  domain: env.get('DOMAIN', 'localhost'),
  publicDir: pathTo(env.get('PUBLIC_DIR', './public')),
  widgetsPath: pathTo(env.get('WIDGETS_PATH', './lib/widgets')),
  widgetsType: env.get('WIDGETS_TYPE', 'j2.html'),
  languagePath: pathTo(env.get('LANGUAGE_PATH', './lib/languages')),
  enableLocalDebugger: env.getBool('ENABLE_LOCAL_DEBUGGER', false),
);

final app = FinchApp(configs: configs);

void main([List<String>? args]) async {
  app.addRouting(getWebRoute);

  app.start(args).then((value) {
    Console.p('Server started: http://localhost:${value.port}');
  });
}

Route Function

Routes are defined in a function that receives a Request and returns Future<List<FinchRoute>>:

// lib/route/web_route.dart
import 'package:finch/finch_route.dart';
import '../controllers/home_controller.dart';

final homeController = HomeController();

Future<List<FinchRoute>> getWebRoute() async {
  return [
    FinchRoute(
      key: 'root',
      path: '/',
      methods: Methods.ONLY_GET,
      index: homeController.index,
    ),
    FinchRoute(
      key: 'root.api',
      path: 'api/hello',
      methods: Methods.ONLY_GET,
      index: homeController.hello,
    ),
  ];
}

Inline Routes (Shorthand)

For simple or quick routes, FinchApp has shorthand methods. The index callback receives the Request as a parameter:

app
  ..get(
    path: '/ping',
    index: (rq) async => rq.renderString(text: 'pong'),
  )
  ..post(
    path: '/echo',
    index: (rq) async {
      var body = rq.get<String>('message', def: '');
      return rq.renderString(text: body);
    },
  )
  ..postGet(
    path: '/form',
    index: (rq) async => rq.renderString(text: 'GET or POST'),
  );

Development: serve.dart with File Watcher

During development, use lib/serve.dart (generated by finch create). It runs main() from app.dart and watches widget and language files for changes, automatically regenerating the Dart source maps without a full server restart:

finch serve -p lib/serve.dart
# or
dart run lib/serve.dart

The watcher calls LanguageToDart and WidgetToDart converters on file changes, then notifies the connected debugger bar to reload the page.

Production: Binary Compilation

Compile to a self-contained native binary:

finch build -a lib/app.dart -o ./build/app
./build/app

Or use the provided Dockerfile / docker-compose. See Docker.

Cron Jobs

Register scheduled tasks with app.registerCron():

app.registerCron(
  FinchCron(
    schedule: FinchCron.evryDay(2),   // Every 2 days
    onCron: (index, cron) async {
      // run cleanup task
    },
    delayFirstMoment: true,
  ).start(),
);

// Standard cron expression
app.registerCron(
  FinchCron(
    schedule: '0 * * * *',  // Every hour
    onCron: (index, cron) async {
      // run hourly task
    },
  ).start(),
);

Handling Logged Errors

Register a callback with app.onError() to be notified whenever an error is logged through Console (e.g. Console.e(...)), for example to forward it to an external monitoring service:

app.onError((error, type) {
  // 'type' is the log level, e.g. 'error'
  MyMonitoringService.report(error, type);
});

Passing Arguments

Always forward args from main to app.start(). This allows CLI commands like migrate and custom commands to work:

void main([List<String>? args]) async {
  app.addRouting(getWebRoute);
  app.start(args);
}
dart run lib/app.dart migrate --init