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['DOMAIN_PORT'] ?? '8080').toInt(def: 8080),
  domain: env['DOMAIN'] ?? 'localhost',
  publicDir: pathTo(env['PUBLIC_DIR'] ?? './public'),
  widgetsPath: pathTo(env['WIDGETS_PATH'] ?? './lib/widgets'),
  widgetsType: env['WIDGETS_TYPE'] ?? 'j2.html',
  languagePath: pathTo(env['LANGUAGE_PATH'] ?? './lib/languages'),
  enableLocalDebugger: (env['ENABLE_LOCAL_DEBUGGER'] ?? false).toString().toBool,
);

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(Request rq) 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(),
);

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