Flutter web app with Dart backend
Shelf makes it easy to create and compose web servers
If you have a Flutter web app and want to extend it with a Dart backend, this article covers the key concepts. A significant advantage of this approach is the shared codebase — common logic such as import, export, and converter methods can be reused across both frontend and backend.
For the dart backend it is essential to use a shelf pub package. This package includes these libraries, that you need to run server and create routes. Let’s look at the following example server.dart and routes.dart in backend directory inside your Flutter app.
backend/server.dart
import 'dart:io';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as shelf_io;
import 'router.dart';
Future main() async {
final router = Router();
final cascade = Cascade().add(router.staticHandler()).add(router.handler());
final port = int.parse(Platform.environment['PORT'] ?? '80');
final server = await shelf_io.serve(
logRequests().addHandler(cascade.handler),
InternetAddress.anyIPv4,
port,
);
server.autoCompress = true;
print('Serving at http://${server.address.host}:${server.port}');
} backend/router.dart
import 'dart:io';
import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart' as shelf_router;
import 'package:shelf_static/shelf_static.dart' as shelf_static;
class Router {
Router();
Handler staticHandler() {
return shelf_static.createStaticHandler(
'web',
defaultDocument: 'index.html',
);
}
Handler handler() {
return shelf_router.Router()
..get('/api/hello', (Request request) {
return Response.ok('Welcome to Dart backend!');
})
..all('/<ignored|.*>', _indexHandler);
}
Response _indexHandler(Request request) {
final indexFile = File('web/index.html').readAsBytesSync();
return Response.ok(indexFile, headers: {'Content-Type': 'text/html'});
}
} Run this server with this command: dart run backend/server.dart
If it starts without errors and the server responds on the specified port and endpoint, you can continue creating the Dockerfile.
Dockerfile
# Stage 1 - Install dependencies and build the app
FROM debian:latest AS builder
# Install flutter dependencies
RUN apt-get update
RUN apt-get install -y curl git wget unzip libgconf-2-4 gdb libstdc++6 libglu1-mesa fonts-droid-fallback lib32stdc++6 python3
RUN apt-get clean
# Clone the flutter repo
RUN git clone https://github.com/flutter/flutter.git /usr/local/flutter
# Set flutter path
# RUN /usr/local/flutter/bin/flutter doctor -v
ENV PATH="/usr/local/flutter/bin:/usr/local/flutter/bin/cache/dart-sdk/bin:${PATH}"
# Change stable channel
RUN flutter channel stable
# Enable web capabilities
RUN flutter config --enable-web
RUN flutter upgrade
RUN flutter pub global activate webdev
# RUN flutter doctor -v
# Copy files to container and build
RUN mkdir /app
COPY . /app
WORKDIR /app
RUN flutter pub get
RUN flutter build web
RUN dart compile exe backend/server.dart -o /app/backend/server
# Stage 2 - Create the run-time image
FROM dart:stable AS base-runner
FROM scratch AS runner
COPY /runtime/ /
RUN mkdir /app
WORKDIR /app
COPY /app/backend/server /app/backend/
COPY /app/build/web /app/web
COPY /app/assets /app/assets
COPY package.json /app/web
EXPOSE 80
CMD ["/app/backend/server"] Conclusion
Combining Flutter web with a Dart backend results in a small Docker image of approximately 24 MB. If you do not need advanced web server configuration, the Dart server alone can serve both the API and the static Flutter web assets efficiently.