dart_node_express

Type-safe Express.js bindings for Dart. Build HTTP servers and REST APIs entirely in Dart.

Installation

dependencies:
  dart_node_express: ^0.11.0-beta

Also install Express via npm:

npm install express

Quick Start

import 'dart:js_interop';
import 'package:dart_node_express/dart_node_express.dart';

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

  app.get('/', handler((req, res) {
    res.send('Hello, Dart!');
  }));

  app.listen(3000, () {
    print('Server running on port 3000');
  }.toJS);
}

Routing

Basic Routes

app.get('/users', handler((req, res) {
  res.jsonMap({'users': []});
}));

app.post('/users', handler((req, res) {
  final body = req.body;
  res.status(201);
  res.jsonMap({'created': true});
}));

app.put('/users/:id', handler((req, res) {
  final id = req.params['id'];
  res.jsonMap({'updated': id});
}));

app.delete('/users/:id', handler((req, res) {
  res.status(204);
  res.end();
}));

Route Parameters

app.get('/users/:userId/posts/:postId', handler((req, res) {
  final userId = req.params['userId'];
  final postId = req.params['postId'];

  res.jsonMap({
    'userId': userId,
    'postId': postId,
  });
}));

Query Parameters

app.get('/search', handler((req, res) {
  final query = req.query['q'];
  final page = int.tryParse(req.query['page'] ?? '1') ?? 1;

  res.jsonMap({
    'query': query,
    'page': page,
  });
}));

Request Object

The Request object provides access to incoming request data:

app.post('/api/data', handler((req, res) {
  // Request body (requires body-parsing middleware)
  final body = req.body;

  // Headers
  final contentType = req.headers['content-type'];

  // URL path
  final path = req.path;

  // HTTP method
  final method = req.method;

  // Query string parameters
  final params = req.query;

  res.jsonMap({'received': body});
}));

Response Object

The Response object provides methods for sending responses:

// Send text
res.send('Hello!');

// Send JSON (for Dart Maps, use jsonMap)
res.jsonMap({'message': 'Hello!'});

// Set status code (separate call from response)
res.status(201);
res.jsonMap({'created': true});

// Set headers
res.set('X-Custom-Header', 'value');

// Redirect
res.redirect('/new-location');

// End response without body
res.status(204);
res.end();

Middleware

Custom Middleware

app.use(middleware((req, res, next) {
  print('${req.method} ${req.path}');
  next();
}));

Chaining Middleware

app.use(chain([
  middleware((req, res, next) {
    print('First middleware');
    next();
  }),
  middleware((req, res, next) {
    print('Second middleware');
    next();
  }),
]));

Request Context

Store and retrieve values in the request context:

// Set context in middleware
app.use(middleware((req, res, next) {
  setContext(req, 'userId', '123');
  next();
}));

// Get context in handler
app.get('/profile', handler((req, res) {
  final userId = getContext<String>(req, 'userId');
  res.jsonMap({'userId': userId});
}));

Router

Organize routes with the Router:

Router createUserRouter() {
  final router = Router();

  router.get('/', handler((req, res) {
    res.jsonMap({'users': []});
  }));

  router.post('/', handler((req, res) {
    res.status(201);
    res.jsonMap({'created': true});
  }));

  router.get('/:id', handler((req, res) {
    res.jsonMap({'user': req.params['id']});
  }));

  return router;
}

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

  // Mount the router
  final router = createUserRouter();
  app.use('/api/users', router);

  app.listen(3000);
}

Async Handlers

Use async handlers for database calls and other async operations:

app.get('/users', asyncHandler((req, res) async {
  final users = await database.fetchUsers();
  res.jsonMap({'users': users});
}));

The asyncHandler wrapper ensures errors are properly caught and passed to error middleware.

Validation

Use the schema-based validation system:

// Define a validated data type
typedef CreateUserData = ({String name, String email, int? age});

// Create a schema
final createUserSchema = schema<CreateUserData>(
  {
    'name': string().minLength(2).maxLength(50),
    'email': string().email(),
    'age': optional(int_().positive()),
  },
  (data) => (
    name: data['name'] as String,
    email: data['email'] as String,
    age: data['age'] as int?,
  ),
);

// Use validation middleware
app.post('/users', validateBody(createUserSchema));
app.post('/users', handler((req, res) {
  final result = getValidatedBody<CreateUserData>(req);
  switch (result) {
    case Success(:final value):
      res.status(201);
      res.jsonMap({'name': value.name, 'email': value.email});
    case Error(:final error):
      res.status(400);
      res.jsonMap({'error': error});
  }
}));

Available Validators

// String validators
string().minLength(2).maxLength(100).notEmpty().email().alphanumeric()

// Integer validators
int_().min(0).max(100).positive().range(1, 10)

// Boolean validators
bool_()

// Optional wrapper
optional(string())

Complete Example

import 'dart:js_interop';
import 'package:dart_node_express/dart_node_express.dart';

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

  // Logging middleware
  app.use(middleware((req, res, next) {
    print('[${DateTime.now()}] ${req.method} ${req.path}');
    next();
  }));

  // Routes
  app.get('/', handler((req, res) {
    res.jsonMap({
      'name': 'My API',
      'version': '1.0.0',
    });
  }));

  app.get('/health', handler((req, res) {
    res.jsonMap({'status': 'ok'});
  }));

  // Mount routers
  app.use('/api/users', createUserRouter());

  // Start server
  app.listen(3000, () {
    print('Server running on port 3000');
  }.toJS);
}

Source Code

The source code is available on GitHub.