flint_dart 1.0.0+29 copy "flint_dart: ^1.0.0+29" to clipboard
flint_dart: ^1.0.0+29 copied to clipboard

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

Flint Dart #

A modern, production‑ready backend framework for Dart. Flint Dart gives you routing, middleware, ORM, authentication, validation, views, and auto‑generated Swagger docs—built for real apps, not just demos.

  • Website: flintdart.eulogia.net
  • Status: Internal Build (v1.0.0+29)
  • Maintainers: Eulogia Technologies

Why Flint Dart #

  • Fast, clean routing for REST APIs
  • Middleware-first design for security and control
  • Built‑in ORM for MySQL and PostgreSQL
  • Auth helpers (JWT + social providers)
  • Validation on requests (JSON + form)
  • Views with a simple template engine
  • Swagger UI auto‑docs from route annotations
  • CLI to scaffold projects and files

Installation #

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

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

Controller-based routes (v1.0.0+29) #

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.body() — raw body string
  • ctx.req.json() — JSON body (Map)
  • ctx.req.form() — form data (Map)

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

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)

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),
        ],
      );
}

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

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

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

10
likes
0
points
25
downloads

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

unknown (license)

Dependencies

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

More

Packages that depend on flint_dart