Flutter web app with Dart backend
Shelf makes it easy to create and compose web servers By: Dániel Sipos onIf you have a flutter app for web and would like to extend it with dart backend, you can learn some important things in this article. My experience comes from the development of the translation management application. A huge advantage is the shared codebase, I use these in I18Nature, for example: import, export and converter methods as common resources.
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"]
Final words
So, there is a simple solution to use flutter web and dart backend together. Finally, we get a small size of the Docker image, about ~24MB, so it is great. If you don’t need an extra web server configuration, I can strongly recommend, that you can use only the dart server to serve static flutter web resources.