flint_dart 1.2.2 copy "flint_dart: ^1.2.2" to clipboard
flint_dart: ^1.2.2 copied to clipboard

A modern, expressive, and extensible server-side framework by Eulogia Technologies.

Flint Dart #

A modern, production‑ready Full-Stack Framework for Dart. Flint Dart empowers you to build complete, end-to-end applications (declarative browser frontend, real-time WebSocket communication, database ORM, mail/SMTP engine, authentication, and background task AI runtime) entirely in Dart.

  • Website: flintdart.dev
  • Status: Stable Release (v1.1.0)
  • Maintainers: Eulogia Technologies

The Full-Stack Dart Advantage #

Flint Dart unifies client-side declarative UI rendering and server-side systems under one language:

  • Frontend & UI (Flint UI): Flutter-like declarative UI widget engine for HTML, email templates, and high-performance server-side rendering (SSR) with progressive loading in the browser.
  • Fast, Clean Routing: REST APIs and route groups with middleware-first security.
  • Real-Time WebSockets: High-performance event-driven sockets with Socket.IO-like rooms and automated model payload serialization.
  • Active Record ORM: Built‑in database mapping for MySQL and PostgreSQL with safe migrations.
  • Request Validation: Auto-normalizes and validates incoming payloads (JSON, URL-encoded, or Multipart/Forms).
  • Authentication: JWT + OAuth + refresh tokens and rate-limiting lockout.
  • Mail Engine: SMTP client with .flint.html template rendering support.
  • Swagger UI: Auto‑generated Swagger documentation from code annotations.
  • Developer CLI: Full-featured toolchain to scaffold files, migrate databases, and run hot-reloads.

Installation #

One-command setup #

Install Dart, FlintDart, Flint UI, and PATH entries in one pass:

# macOS/Linux
curl -fsSL https://raw.githubusercontent.com/flint-dart/flint_dart/main/install.sh | sh
# Windows PowerShell
iwr https://raw.githubusercontent.com/flint-dart/flint_dart/main/install.ps1 -UseB | iex

See INSTALL.md for options and OS-specific details.

For packaged releases, Flint also has a compiled Rust installer:

cd flint/installer-app
cargo run --bin flint-install

1) Install CLI globally #

# Activate CLI

dart pub global activate flint_dart

# Create a new project
flint create my_app
cd my_app

# Run the app
flint run

2) Add to an existing project #


dart pub add flint_dart

Hello World #

import 'package:flint_dart/flint_dart.dart';

void main() {
  final app = Flint();

  app.get('/', (Context ctx) async {
    return ctx.res?.send('Welcome to Flint Dart!');
  });

  app.listen(port: 3000);
}

Deployment Migrations #

Flint can run the normal table registry migration path once during server startup, before the HTTP port is bound:

final app = Flint(autoMigrate: true);

You can also enable it per environment without changing code:

FLINT_AUTO_MIGRATE=true
FLINT_AUTO_MIGRATE_CREATE_DB=false
FLINT_AUTO_MIGRATE_VERBOSE=false

Auto migration is off by default. When enabled, it runs once per server process and uses the same migration engine as flint migrate.


Routing #

app.get('/users/:id', (Context ctx) async {
  final id = ctx.req.param('id');
  return ctx.res?.json({'id': id});
});

app.post('/users', (Context ctx) async {
  final body = await ctx.req.json();
  return ctx.res?.json({'created': true, 'data': body});
});

HTTP QUERY routes #

Flint supports the HTTP QUERY method from RFC 10008. QUERY is safe and idempotent like GET, but it can carry request content like POST. Use it for complex search or filter operations where the query expression is too large or too structured for a URL query string, but the operation must not mutate server state.

app.query('/products/search', (Context ctx) async {
  final filters = await ctx.req.json();
  final page = ctx.req.queryParam('page') ?? '1';

  return ctx.res?.json({
    'filters': filters,
    'page': page,
  });
});

ctx.req.query and ctx.req.queryParam() still read URI query string values such as ?page=2. The QUERY request body is separate query content and can use the same JSON, text, and form helpers as other body-carrying routes.

Compatibility note: some proxies, API gateways, browsers, servers, and API tools may not support QUERY yet. See RFC 10008: https://www.rfc-editor.org/rfc/rfc10008.html.

Controller-based routes (v1.0.2) #

Flint also supports request-scoped controllers for both HTTP and WebSocket routes. Controller methods can use req, res, and socket directly after binding.

import 'package:flint_dart/flint_dart.dart';

class UserController extends Controller {
  Future<Response> create() async {
    final body = await req.json();
    return res.json({'created': true, 'data': body});
  }
}

class ChatController extends Controller {
  void connect() {
    socket.emit('connected', {'id': socket.id});
  }
}

Route registration:

app.post(
  '/users',
  controllerAction(UserController.new, (c) => c.create()),
);

app.websocket(
  '/chat',
  controllerAction(ChatController.new, (c) {
    c.connect();
    return null;
  }),
);

RouteGroup shorthand (no mixin required):

class UserRoutes extends RouteGroup {
  @override
  String get prefix => '/users';

  @override
  void register(Flint app) {
    app.post('/', useController(UserController.new, (c) => c.create()));
  }
}

Unified Context handlers #

Flint uses a unified Context object for route handlers.

  • ctx.req is always available.
  • ctx.res is available for HTTP routes.
  • ctx.socket is available for WebSocket routes.
  • ctx.extras and ctx.read<T>() / ctx.write<T>() support future request-scoped injection (e.g. session/user).
app.get('/health', (Context ctx) {
  return ctx.res?.json({'ok': true});
});

app.websocket('/chat', (Context ctx) {
  ctx.socket?.on('ping', (_) {
    ctx.socket?.emit('pong', {'ok': true});
  });
});

Request helpers #

  • ctx.req.param('id') — route parameter
  • ctx.req.queryParam('page') — query parameter
  • ctx.req.rawBody() — raw body bytes for custom decoding/signature checks
  • ctx.req.body() — raw body string
  • ctx.req.input('key') — normalized input from query, JSON, form, multipart, files, and params
  • ctx.req.allInput() — all normalized input
  • ctx.req.validate({...}) — auto-detects request input and validates it
  • ctx.req.json() — JSON body (Map)
  • ctx.req.form() — text form fields only (urlencoded or multipart)
  • ctx.req.file('avatar') / ctx.req.files('gallery') — uploaded files

Response helpers #

  • ctx.res?.send('text')
  • ctx.res?.json({...})
  • ctx.res?.view('home', data: {...})
  • ctx.res?.respond(data) — auto‑detects type
  • ctx.res?.back(fallback: '/settings') — redirect to previous URL
  • ctx.res?.withSuccess('Saved') / ctx.res?.withError('Failed') — flash messages for next template render

You can also return data directly from a handler. Flint will serialize:

  • Map / List as JSON
  • primitives as text/auto response
  • custom objects that implement toMap() or toJson()
app.post('/settings', (Context ctx) async {
  final data = await ctx.req.validate({'name': 'required|string|min:2'});
  // ... persist data
  return ctx.res
      ?.withSuccess('Settings updated successfully.')
      .back(fallback: '/settings');
});
class UserDto {
  final int id;
  final String email;
  UserDto(this.id, this.email);

  Map<String, dynamic> toMap() => {'id': id, 'email': email};
}

app.get('/me', (Context ctx) async {
  return UserDto(1, 'ada@example.com'); // auto JSON via toMap()
});

Middleware #

Global middleware #

app.use(AuthMiddleware());

Route middleware #

app.get('/admin', handler).use(AuthMiddleware());

Route group middleware #

class AdminRoutes extends RouteGroup {
  @override
  String get prefix => '/admin';

  @override
  List<Middleware> get middlewares => [AuthMiddleware()];

  @override
  void register(Flint app) {
    app.get('/users', (Context ctx) async => ctx.res?.json([]));
  }
}

Built‑in middleware:

  • ExceptionMiddleware — error handling
  • CookieSessionMiddleware — cookies/sessions
  • CorsMiddleware — CORS headers
  • LoggerMiddleware — request logging
  • StaticFileMiddleware — static files

Validation #

app.post('/register', (Context ctx) async {
  final data = await ctx.req.validate({
    'name': 'required|string|min:3',
    'email': 'required|email',
    'password': 'required|string|min:8',
  }, messages: {
    'email.required': 'Email is required.',
    'password.min': 'Password must be at least :min characters.',
  });

  return ctx.res?.json({'ok': true, 'data': data});
});

validate(), input(), and allInput() now normalize incoming data for you, so the same handler can accept:

  • JSON requests
  • application/x-www-form-urlencoded
  • multipart/form-data

Example:

app.post('/profile', (Context ctx) async {
  final data = await ctx.req.validate({
    'name': 'required|string|min:2',
    'email': 'required|email',
    'avatar': 'required',
  });

  final name = await ctx.req.input('name');
  final avatar = await ctx.req.input('avatar'); // UploadedFile for multipart

  return ctx.res?.json({
    'ok': true,
    'name': name,
    'hasAvatar': avatar != null,
    'data': data,
  });
});

Use the lower-level helpers only when you need a specific payload shape:

  • json() when you want a strict JSON object
  • form() when you only want text form fields
  • file() / files() when working directly with uploads
  • rawBody() when you need the exact undecoded payload

WebSockets #

WebSocket emits now normalize common Dart values before JSON encoding, so you can send normal app objects without manually converting everything first.

app.websocket('/chat', (Context ctx) {
  ctx.socket?.emit('connected', {
    'id': ctx.socket?.id,
    'connectedAt': DateTime.now(),
    'profile': UserDto(1, 'ada@example.com'),
  });
});

That means values like DateTime, nested List/Map data, and custom objects with toMap() or toJson() are converted safely before they are sent over the socket.


Views & Templates #

Flint’s view engine uses .flint.html templates with a small set of processors. Views live in lib/views and can extend layouts, include partials, loop, and render variables.

Render a view #

app.get('/', (Context ctx) async {
  return ctx.res?.view('home', data: {'title': 'Flint Docs'});
});

Layouts, sections, and yield #

<!-- lib/views/layouts/app.flint.html -->
<!doctype html>
<html>
  <head>
    <title>{{ title ?? 'Flint' }}</title>
  </head>
  <body>
    {{ yield('content') }}
  </body>
</html>
<!-- lib/views/home.flint.html -->
{{ extends('layouts.app') }}

{{ section('content') }}
  <h1>{{ title }}</h1>
{{ endsection }}

Includes (partials) #

{{ include('partials.nav') }}

Variables #

<h2>{{ user.name }}</h2>

Conditionals #

{{ if user }}
  <p>Welcome back, {{ user.name }}</p>
{{ endif }}

Loops #

<ul>
  {{ for item in items }}
    <li>{{ item }}</li>
  {{ endfor }}
</ul>

Switch / Case #

{{ switch status }}
  {{ case 'paid' }}<span>Paid</span>{{ endcase }}
  {{ case 'pending' }}<span>Pending</span>{{ endcase }}
  {{ default }}<span>Unknown</span>{{ enddefault }}
{{ endswitch }}

Built‑in Processors #

  • extends — layout inheritance
  • section / yield — slot content into layouts
  • include — partials/partials with data
  • variables{{ ... }} interpolation
  • if_statementif/endif
  • for_loopfor/endfor
  • switch_casesswitch/case/default
  • comment — template comments
  • assets — asset helper tags
  • session — session/error helpers in templates
  • old_processor — legacy tags support

Mail (SMTP + Flint Templates) #

Flint supports sending email with SMTP and rendering email bodies from .flint.html templates.

Auto connect mail on app start #

Mail config is loaded automatically when the server starts:

final app = Flint(); // autoConnectMail is true by default
await app.listen(port: 3000);

If you want full manual control:

final app = Flint(autoConnectMail: false);
await MailConfig.load(); // manual setup from .env
await app.listen(port: 3000);

.env mail config #

MAIL_PROVIDER=custom   # custom | gmail | outlook | yahoo
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=your_username
MAIL_PASSWORD=your_password
MAIL_ENCRYPTION=tls    # tls | ssl
MAIL_FROM_ADDRESS=noreply@example.com
MAIL_FROM_NAME=My App

Send mail directly #

await Mail()
    .to('user@example.com')
    .subject('Welcome')
    .html('<h1>Hello</h1>')
    .text('Hello')
    .sendMail();

Text-only mail:

await Mail()
    .to('user@example.com')
    .subject('Plain Text Message')
    .text('Hello from Flint Mail')
    .sendMail();

Use Flint view templates for mail (ViewMailable) #

Generate a mail class + template:

flint make:mail welcome

This creates:

  • lib/mail/welcome_mail.dart
  • lib/mail/views/welcome.flint.html

Example:

import 'package:flint_dart/mail.dart';

class WelcomeMail extends ViewMailable {
  final String recipientName;
  final String recipientEmail;

  WelcomeMail({
    required this.recipientName,
    required this.recipientEmail,
  });

  @override
  String get subject => 'Welcome';

  @override
  String get view => 'mail/views/welcome.flint.html';

  @override
  Map<String, dynamic> get data => {
        'recipientName': recipientName,
      };

  @override
  List<String> get to => [recipientEmail];
}

await WelcomeMail(
  recipientName: 'Ada',
  recipientEmail: 'ada@example.com',
).send();

Template rendering notes #

  • For web pages, use ctx.res?.view('home', data: {...}) with templates in lib/views.
  • For mail templates, ViewMailable renders your .flint.html file via TemplateEngine().render(...).
  • If you are looking for old names like viewFlintTemplate or Viewable, use the current APIs above (view, TemplateEngine, ViewMailable).

ORM (CRUD) #

// READ
final user = await User().find(1);
final users = await User()
  .where('email', 'test@example.com')
  .orderBy('created_at', desc: true)
  .limit(10)
  .get();

// CREATE
final created = await User().create({
  'name': 'Ada',
  'email': 'ada@example.com',
  'password': 'secret',
});

// UPDATE
await User()
  .where('id', 1)
  .update(data: {'name': 'Ada Lovelace'});

// DELETE
await User().delete(1);

Extra ORM helpers #

  • save() — create/update based on PK
  • firstOrCreate(where, data)
  • upsert(where, data) / upsertMany([...])
  • countAll() / countWhere(field, value)

Seeders #

Flint seeders are meant to run through a registry file.

The main entry point is:

lib/config/seeder_registry.dart

That registry is what flint --db-seed runs, and it is the file the seeder generator updates when you create new seeders.

Example registry:

import 'package:flint_dart/flint_dart.dart';
import '../seeders/user_model_seeder.dart';
import '../seeders/post_model_seeder.dart';

Future<void> main() async {
  await runSeeders([
    UserModelSeeder(),
    PostModelSeeder(),
  ]);
}

Create a seeder:

flint make:seeder UserModelSeeder

Run all registered seeders:

flint --db-seed

If you want people on your team to follow one pattern, this is the one to emphasize:

  • create seeders in lib/seeders
  • register them in lib/config/seeder_registry.dart
  • run them through flint --db-seed

Models & Tables #

class User extends Model<User> {
  User() : super(() => User());

  String get name => getAttribute('name');
  String get email => getAttribute('email');

  @override
  Table get table => Table(
        name: 'users',
        columns: [
          Column(name: 'name', type: ColumnType.string, length: 255),
          Column(name: 'email', type: ColumnType.string, length: 255),
        ],
      );
}

Models In Flint UI #

FlintDart models are server-side objects. Keep them in controllers, services, jobs, and seeders where database access is available.

When sending data to Flint UI, convert models to maps:

class UiController {
  Future<Response> dashboard(Request req, Response res) async {
    final users = await User().all();

    return res.page(
      'Dashboard',
      title: 'Dashboard',
      script: '/main.dart.js',
      props: {
        'users': users.map((user) => user.toMap()).toList(),
      },
    );
  }
}

Then refresh the same resource in Flint UI with FlintModelApi:

final users = ResourceController<List<FlintModelRecord>>(
  loader: () => FlintModelApi<FlintModelRecord>.records('/users').list(),
);

Think of the bridge as:

Model<T> on server -> JSON -> FlintModelRecord or typed DTO in browser

That keeps database code out of the browser while preserving a model-shaped developer experience.


Relations #

class User extends Model<User> {
  @override
  Map<String, RelationDefinition> get relations => {
        'posts': Relations.hasMany('posts', () => Post()),
      };
}

class Post extends Model<Post> {
  @override
  Map<String, RelationDefinition> get relations => {
        'author': Relations.belongsTo('author', () => User()),
      };
}

final posts = await Post().withRelation('author').get();

Auth (JWT + Providers) #

Register / Login #

final user = await Auth.register(
  email: data['email'],
  password: data['password'],
  name: data['name'],
  additionalData: {'role': 'admin'},
);

final result = await Auth.login(data['email'], data['password']);

Access + Refresh Tokens (Optional) #

Refresh token support is opt-in and disabled by default to keep framework behavior flexible.

final tokens = await Auth.loginWithTokens(
  data['email'],
  data['password'],
  throttleKey: req.ipAddress, // optional
  ipAddress: req.ipAddress,   // optional metadata
  userAgent: req.headers['user-agent'],
  deviceName: 'web',
);

final rotated = await Auth.refreshAccessToken(
  tokens['refreshToken'],
  rotateRefreshToken: true,
);

Revoke helpers:

await Auth.revokeRefreshToken(refreshToken);
await Auth.revokeAllRefreshTokensForUser(userId);

Login Throttle (Optional) #

Login throttle/lockout is also opt-in and disabled by default.

final result = await Auth.login(
  data['email'],
  data['password'],
  throttleKey: req.ipAddress, // optional (ip/device/etc)
);

OAuth Providers #

final url = Auth.providerRedirectUrl(
  provider: 'google',
  redirectPath: '/auth/google/callback',
);

Supported providers: Google, GitHub, Facebook, Apple.

Auth Environment Options #

# Existing
JWT_SECRET=change-me-to-a-strong-secret
JWT_EXPIRY_HOURS=24
PASSWORD_MIN_LENGTH=6

# Optional refresh-token flow
AUTH_ENABLE_REFRESH_TOKENS=false
AUTH_ACCESS_TOKEN_MINUTES=60
AUTH_REFRESH_TOKEN_DAYS=30

# Optional login throttle
AUTH_ENABLE_LOGIN_THROTTLE=false
AUTH_LOGIN_MAX_ATTEMPTS=5
AUTH_LOGIN_LOCK_MINUTES=15

File Uploads & Storage #

final upload = await ctx.req.file('avatar');
if (upload != null) {
  final path = await ctx.req.storeFile('avatar', directory: 'public/uploads');
}

AI Runtime #

Flint now exposes one AI system only: ai.

The AI layer is organized around:

  • providers
  • tools
  • workflows
  • memory
  • runtime

Framework entry points #

final app = Flint();

app.ai.useOpenAiFromEnv();

app.get('/ai/chat', (Context ctx) async {
  final result = await ctx.ai.chat(
    providerId: 'openai',
    request: const ChatRequest(
      model: 'gpt-4o-mini',
      messages: [
        ChatMessage(role: 'user', content: 'Say hello'),
      ],
    ),
  );

  return ctx.res?.json(result.toMap());
});

Provider credentials can come from .env:

app.ai.useOpenAiFromEnv();
app.ai.useGeminiFromEnv();
app.ai.useAnthropicFromEnv();

// Or register every provider that has credentials configured.
final providers = app.ai.useChatProvidersFromEnv();

Supported keys are OPENAI_API_KEY, OPENAI_BEARER_TOKEN, GEMINI_API_KEY, GEMINI_BEARER_TOKEN, ANTHROPIC_API_KEY, plus optional *_CHAT_ENDPOINT overrides.

You can also opt into stricter production defaults:

final ai = FlintAi.production(
  memoryStore: FlintAutoAiMemoryStore(),
  repository: FlintAutoAiRepository(),
);

Register providers, tools, workflows, memory, and persistence #

final app = Flint();

app.ai.useChatProvidersFromEnv();
app.ai.registerTool(SummarizeTicketTool());
app.ai.registerWorkflow(SupportEscalationWorkflow());

// Optional explicit production wiring
final hardenedAi = FlintAi.production(
  memoryStore: FlintAutoAiMemoryStore(),
  repository: FlintAutoAiRepository(),
);

Tool policy can also be hardened from .env:

app.ai.useProductionToolPolicyFromEnv();

await ctx.ai.run(
  agent: SupportAgent(),
  goal: const AiGoal(task: 'Summarize ticket'),
  userId: staff.id.toString(),
  metadata: {
    'role': staff.role,
    'capabilities': ['support:write'],
  },
);

Use AI_ALLOWED_TOOLS, AI_ALLOWED_CAPABILITIES, and AI_ALLOWED_ROLES for explicit allow-lists.

Runtime model #

  • AiAgent defines planning and synthesis
  • AiTool performs controlled side effects
  • AiWorkflow handles higher-level orchestration
  • AiMemoryStore tracks run memory
  • AiRepository persists runs, traces, threads, and artifacts

Memory and persistence APIs #

await ctx.ai.saveThreadMessage('thread-42', {
  'role': 'user',
  'content': 'Customer cannot reset password',
});

final thread = await ctx.ai.loadThreadMessages('thread-42');
final events = await ctx.ai.loadRunEvents('run-id');

Production note #

Flint uses auto-configured AI memory and repository stores by default:

  • when the database is connected, DB-backed stores are used
  • when it is not connected, Flint falls back to in-memory stores with warnings

For production, connect the database or provide explicit shared stores so runs, traces, threads, and memory survive restarts and work across workers.

Built-in chat providers #

  • OpenAiChatProvider
  • AnthropicChatProvider
  • GeminiChatProvider

These live under ai and use the shared AI provider abstractions. There is no separate public agent namespace anymore.

End-to-end example #

class SummarizeTicketTool extends AiTool {
  @override
  String get name => 'support.summarize';

  @override
  String get description => 'Formats a support summary.';

  @override
  bool get enabledByDefault => true;

  @override
  Future<Map<String, dynamic>> execute(AiToolContext context) async {
    final issue = context.arguments['issue']?.toString() ?? '';
    return {'summary': 'Customer issue: $issue'};
  }
}

class SupportAgent extends AiAgent {
  @override
  String get name => 'support_agent';

  @override
  Future<AiPlan> plan(AiRunContext context) async {
    return AiPlan(
      steps: [
        AiPlanStep(
          id: 'summarize',
          type: 'tool',
          description: 'Create a support summary',
          toolName: 'support.summarize',
          arguments: {'issue': context.goal.input['issue']},
        ),
      ],
    );
  }

  @override
  Future<Map<String, dynamic>> synthesize(AiRunContext context) async {
    return {
      'summary': (context.state['summarize'] as Map)['summary'],
      'events': context.run.events.map((event) => event.toMap()).toList(),
    };
  }
}

final app = Flint();
app.ai.registerTool(SummarizeTicketTool());

app.post('/ai/support', (Context ctx) async {
  final body = await ctx.req.json();
  final threadId = body['threadId']?.toString() ?? 'support-thread';

  await ctx.ai.saveThreadMessage(threadId, {
    'role': 'user',
    'content': body['issue'],
  });

  final run = await ctx.ai.run(
    agent: SupportAgent(),
    goal: AiGoal(
      task: 'Resolve support request',
      input: {'issue': body['issue']},
    ),
    userId: 'support-user',
    threadId: threadId,
    context: ctx,
  );

  return ctx.res?.json({
    'run': run.toMap(),
    'thread': await ctx.ai.loadThreadMessages(threadId),
    'events': await ctx.ai.loadRunEvents(run.run.id),
  });
});

The sample app also includes real agent endpoints:

  • POST /ai-demo/support
  • GET /ai-demo/reporting
  • POST /ai-demo/content-email

Swagger UI (Auto Docs) #

Enable docs and visit:

http://localhost:3000/docs

Flint parses annotations above your routes:

/// @summary Create a new user
/// @auth bearer
/// @response 201 Created
/// @query page integer optional Page
/// @body {"name": "string", "email": "string"}
app.post('/users', controller.create);

HTTP QUERY routes are preserved in generated docs as Flint vendor extensions, because OpenAPI 3.0 does not define a standard query operation key. Flint does not map them to GET or POST; it emits x-http-method: QUERY and a top-level x-flint-query-routes section so tools can discover the route without changing its semantics.


Database #

Configure .env:

DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3306
DB_NAME=flint
DB_USER=root
DB_PASSWORD=secret
DB_SECURE=false

Auto‑connect runs on server start. You can disable it and call DB.connect() manually.

final app = Flint(autoConnectDb: false);

try {
  await DB.connect(
    database: env('DB_NAME', 'app_db'),
    host: env('DB_HOST', 'localhost'),
    port: env('DB_PORT', 3306),
    username: env('DB_USER', 'root'),
    password: env('DB_PASSWORD', ''),
  );
} catch (e) {
  Log.debug('Database connection failed: $e');
}

Logging #

By default, Flint logs to console and does not create log files.

Use .env to control logging:

LOG_ENABLED=true
LOG_TO_CONSOLE=true
LOG_TO_FILE=false
LOG_DIR=logs
LOG_LEVEL=debug
  • LOG_TO_FILE=false prevents creating log files on user systems.
  • Set LOG_TO_FILE=true only when you want persisted log files.

CLI #

flint create my_app
flint run
flint build
flint docs:generate
flint make:model User
flint make:controller UserController
flint make:middleware AuthMiddleware

Contributing #

Contributions are welcome. Open issues, suggest improvements, or submit PRs.


License #

MIT

13
likes
150
points
726
downloads

Documentation

API reference

Publisher

verified publisherflintdart.eulogia.net

Weekly Downloads

A modern, expressive, and extensible server-side framework by Eulogia Technologies.

Homepage
Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

args, bcrypt, crypto, dart_jsonwebtoken, flint_ai, flint_client, flint_ui, mailer, mime, mysql_dart, package_config, path, postgres, uuid, watcher, worker_manager

More

Packages that depend on flint_dart