Skip to content

Gisila framework

gisila is a small, opinionated web framework for Dart servers, built on Shelf. You write thin controller classes with annotations; a build_runner generator turns them into routes with parameter binding, validation, permission checks and per-route limits. Everything around the routes (auth, CORS, rate limiting, security headers, logging, metrics, database sessions, graceful shutdown) is configured in one AppConfig.

dependencies:
gisila: ^0.4.1
dev_dependencies:
build_runner: ">=2.5.0 <3.0.0"

package:gisila/gisila.dart is the only import you need in application code; it re-exports the parts of shelf and shelf_router that controllers use.

lib/controllers/users_controller.dart
import 'package:gisila/gisila.dart';
part 'users_controller.g.dart';
class CreateUserForm extends Form {
final email = EmailField(name: 'email', required: true);
final name = StringField(name: 'name', required: true, maxLength: 100);
@override
List<FormField<Object?>> collectFields() => [email, name];
}
@Controller('/users')
@RequireAuth()
class UsersController {
@Get('/{id}')
@Permissions({'users:read'})
Future<Map<String, Object?>> show(String id, RequestContext ctx) async =>
{'id': id, 'requestedBy': ctx.principal!.id};
@Post('/')
@Permissions({'users:write'})
Future<Map<String, Object?>> create(CreateUserForm form) async =>
{'email': form.email.value, 'name': form.name.value};
}
bin/server.dart
import 'package:gisila/gisila.dart';
import 'package:my_api/controllers/users_controller.dart';
Future<void> main() async {
final app = GisilaApp(
config: AppConfig(
authenticator: const StaticTokenAuthenticator({
'dev-token': Principal(id: 'dev', permissions: {'users:read', 'users:write'}),
}),
),
);
app.registerController(attacher: UsersController().attachToApp);
await app.start(port: 8080, stopOnSignals: true);
}
Terminal window
dart run build_runner build --delete-conflicting-outputs
dart run bin/server.dart
curl -H 'authorization: Bearer dev-token' localhost:8080/users/7
# {"id":"7","requestedBy":"dev"}

id comes from the path, the form is parsed and validated before your method runs, missing credentials answer 401, missing permissions 403, and @Post answers 201.

  • JSON APIs and backends in Dart, especially alongside Flutter apps that share models.
  • Teams that want routes, validation and access rules declared next to the code, with the wiring generated rather than hand-written.
  • Services that need production basics (auth, rate limits, security headers, structured logs, metrics, graceful shutdown) without assembling them from separate packages.
  • Projects using the rest of the Gisila stack: gisila_orm for Postgres, gisila_queue for background jobs, gisila_doc for OpenAPI specs, gisila_studio for an admin, and the panel to deploy.
  • You want no code generation. gisila’s routing depends on build_runner. Plain shelf_router gives you hand-written routing with nothing generated.
  • Server-rendered HTML sites. gisila can serve static files and return any Response, but it has no templating, sessions or CSRF protection built in.
  • Unusual routing (regex paths, content negotiation per route). Possible with hand-written handlers inside a gisila app, but that is not where the framework helps.