Jaspr can integrate with any Dart backend framework, giving you flexibility to use your preferred server architecture while rendering Jaspr components.
Overview
Instead of using Jaspr’s built-in server, you can:
- Integrate with existing Dart backend frameworks
- Use custom server logic and middleware
- Leverage framework-specific features (routing, databases, etc.)
- Gradually adopt Jaspr in existing projects
Integration with Shelf
Shelf is Dart’s standard HTTP middleware framework.
Setup
Add dependencies:
dependencies:
jaspr: ^latest
shelf: ^latest
shelf_router: ^latest
Basic Integration
import 'dart:io';
import 'package:jaspr/server.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as shelf_io;
import 'package:shelf_router/shelf_router.dart';
import 'app.dart';
void main() async {
// Initialize Jaspr
Jaspr.initializeApp();
final router = Router();
// Regular shelf route
router.get('/api/hello', (request) {
return Response.ok('Hello from Shelf API');
});
// Mount Jaspr app on a specific path
router.mount(
'/app',
serveApp((request, render) {
// Access the shelf request
print("Request: ${request.requestedUri}");
// Render Jaspr components
return render(
Document(
base: '/app', // Set base path for assets
body: App(),
),
);
}),
);
// Serve manually rendered components
router.get('/hello', (request) async {
final html = await renderComponent(
Document(
base: '/app',
body: Hello(),
),
);
return Response.ok(
html,
headers: {'Content-Type': 'text/html'},
);
});
// Create handler with middleware
final handler = const Pipeline()
.addMiddleware(logRequests())
.addHandler(router.call);
// Start server
final port = int.parse(Platform.environment['PORT'] ?? '8080');
final server = await shelf_io.serve(
handler,
InternetAddress.anyIPv4,
port,
shared: true,
);
server.autoCompress = true;
print('Serving at http://${server.address.host}:${server.port}');
}
Set the base parameter on the Document to ensure assets load from the correct path when mounting on routes other than /.
Accessing Request Data
Pass request data to your components:
router.get('/user/<id>', (Request request, String id) async {
return serveApp((request, render) {
return render(
Document(
body: UserProfile(userId: id),
),
);
})(request);
});
Integration with Dart Frog
Dart Frog is a fast, minimalist backend framework.
Setup
Install Dart Frog:
dart pub global activate dart_frog_cli
Create a helper for Jaspr integration:
// lib/utils.server.dart
import 'package:dart_frog/dart_frog.dart';
import 'package:jaspr/server.dart';
import 'main.server.options.dart';
/// Initialize Jaspr for dart_frog
Middleware serveJasprApp() {
Jaspr.initializeApp(options: defaultServerOptions);
return provider<Handler>((context) {
return (request) {
return Handler.sync((req) => Response(statusCode: 404))(request);
};
});
}
/// Render a Jaspr component as a Response
Future<Response> renderJasprComponent(
RequestContext context,
Component component,
) async {
final html = await renderComponent(
Document(body: component),
);
return Response(
body: html,
headers: {'Content-Type': 'text/html'},
);
}
Middleware Setup
// routes/_middleware.dart
import 'package:dart_frog/dart_frog.dart';
import '../lib/utils.server.dart';
/// Jaspr middleware to serve assets
var jasprMiddleware = serveJasprApp();
Handler middleware(Handler handler) {
return handler.use(jasprMiddleware);
}
Route Handler
// routes/counter/index.dart
import 'package:dart_frog/dart_frog.dart';
import '../../lib/components/counter.dart';
import '../../lib/utils.server.dart';
Future<Response> onRequest(RequestContext context) {
return renderJasprComponent(context, Counter());
}
Dart Frog requires the middleware to be set up correctly to serve Jaspr’s compiled JavaScript files.
Integration with Serverpod
Serverpod is a full-featured backend framework with ORM and API generation.
Setup
// lib/server.dart
import 'package:jaspr/server.dart';
import 'package:serverpod/serverpod.dart';
import 'generated/protocol.dart';
import 'generated/endpoints.dart';
import 'web/routes/root.dart';
void main(List<String> args) async {
// Initialize Serverpod
final pod = Serverpod(
args,
Protocol(),
Endpoints(),
);
// Initialize Jaspr
Jaspr.initializeApp();
// Add Jaspr routes to Serverpod's web server
pod.webServer.addRoute(RootRoute(), '/*');
// Start server
await pod.start();
}
Custom Route Handler
// lib/src/web/routes/root.dart
import 'package:jaspr/server.dart';
import 'package:serverpod/serverpod.dart';
import '../../components/app.dart';
class RootRoute extends WidgetRoute {
@override
Future<Widget> build(Session session, HttpRequest request) async {
// Access Serverpod session
final user = await session.auth.authenticatedUser;
return Document(
body: App(user: user),
);
}
}
Manual Component Rendering
Render components to HTML strings for maximum control:
import 'package:jaspr/server.dart';
Future<String> renderMyComponent() async {
final html = await renderComponent(
Document(
head: [
meta(charset: 'utf-8'),
meta(name: 'viewport', content: 'width=device-width'),
title([text('My App')]),
],
body: MyComponent(),
),
);
return html;
}
Use in any HTTP handler:
router.get('/page', (Request request) async {
final html = await renderMyComponent();
return Response.ok(
html,
headers: {'Content-Type': 'text/html; charset=utf-8'},
);
});
Serving Static Assets
Jaspr generates JavaScript and other assets that must be served:
With Shelf
import 'package:shelf_static/shelf_static.dart';
router.mount(
'/web',
createStaticHandler(
'build/jaspr/web',
defaultDocument: 'index.html',
),
);
Custom Handler
import 'dart:io';
router.get('/web/<path|.*>', (Request request, String path) async {
final file = File('build/jaspr/web/$path');
if (!await file.exists()) {
return Response.notFound('Not Found');
}
final bytes = await file.readAsBytes();
final mimeType = _getMimeType(path);
return Response.ok(
bytes,
headers: {
'Content-Type': mimeType,
'Cache-Control': 'public, max-age=31536000',
},
);
});
String _getMimeType(String path) {
if (path.endsWith('.js')) return 'application/javascript';
if (path.endsWith('.css')) return 'text/css';
if (path.endsWith('.html')) return 'text/html';
if (path.endsWith('.json')) return 'application/json';
return 'application/octet-stream';
}
Database Integration
Integrate with databases through your backend framework:
import 'package:postgres/postgres.dart';
import 'package:jaspr/server.dart';
class DatabaseService {
late PostgreSQLConnection db;
Future<void> connect() async {
db = PostgreSQLConnection(
'localhost', 5432, 'mydb',
username: 'user',
password: 'pass',
);
await db.open();
}
Future<List<User>> getUsers() async {
final results = await db.query('SELECT * FROM users');
return results.map((row) => User.fromRow(row)).toList();
}
}
// In your route handler
router.get('/users', (Request request) async {
final users = await db.getUsers();
return serveApp((request, render) {
return render(
Document(
body: UserList(users: users),
),
);
})(request);
});
Authentication
Implement authentication in your backend:
import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart';
// Authentication middleware
Middleware authMiddleware() {
return (Handler handler) {
return (Request request) async {
final token = request.headers['Authorization'];
if (token == null || !await validateToken(token)) {
return Response.forbidden('Unauthorized');
}
final user = await getUserFromToken(token);
// Add user to request context
return handler(
request.change(context: {'user': user}),
);
};
};
}
// Use in routes
final protectedRouter = Router();
protectedRouter.get('/dashboard', (Request request) async {
final user = request.context['user'] as User;
return serveApp((request, render) {
return render(
Document(
body: Dashboard(user: user),
),
);
})(request);
});
// Apply middleware
final handler = const Pipeline()
.addMiddleware(authMiddleware())
.addHandler(protectedRouter.call);
Best Practices
Separate concerns
Keep backend logic in your framework’s handlers, use Jaspr only for rendering UI.
Initialize Jaspr once
Call Jaspr.initializeApp() once at application startup, not in every handler.
Handle asset paths
Set the base parameter correctly when mounting on non-root paths.
Use middleware
Leverage your framework’s middleware for cross-cutting concerns like logging and auth.
Cache rendered output
For static content, cache rendered HTML to improve performance.
Example: Full Shelf Integration
import 'dart:io';
import 'package:jaspr/server.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as shelf_io;
import 'package:shelf_router/shelf_router.dart';
import 'package:shelf_static/shelf_static.dart';
void main() async {
Jaspr.initializeApp();
final app = Router();
// API routes
final api = Router()
..get('/users', _getUsers)
..get('/users/<id>', _getUser)
..post('/users', _createUser);
app.mount('/api', api);
// Static files
app.mount('/static', createStaticHandler('public'));
// Jaspr web assets
app.mount('/web', createStaticHandler('build/jaspr/web'));
// Jaspr app
app.mount('/', serveApp((request, render) {
return render(Document(body: App()));
}));
// Middleware pipeline
final handler = Pipeline()
.addMiddleware(logRequests())
.addMiddleware(corsHeaders())
.addHandler(app.call);
// Start server
final server = await shelf_io.serve(
handler,
'localhost',
8080,
);
server.autoCompress = true;
print('Server running on http://localhost:8080');
}
Middleware corsHeaders() {
return (handler) => (request) async {
final response = await handler(request);
return response.change(headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE',
});
};
}
This integration approach gives you full control over your backend architecture while leveraging Jaspr’s powerful component rendering system.