Skip to main content
BLOG.siposdani87

Flutter web app with Dart backend

Shelf makes it easy to create and compose web servers

By Dániel Sipos
· · 3 min read

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 --from=base-runner /runtime/ /

RUN mkdir /app
WORKDIR /app

COPY --from=builder /app/backend/server /app/backend/
COPY --from=builder /app/build/web /app/web
COPY --from=builder /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.

Share with your friends

Related posts