Skip to content

Fullstack Furniture App with Dart and Flutter Part One

Posted on:May 6, 2023 at 04:00 PM

image

Flutter baked is an open source project that uses Flutter/Dart to build corss-platform applications with various tech stacks.

Table of contents

Open Table of contents

Project Description

furni is a furniture app which consists of the following:

  1. Restful Dart server using alfred
  2. Client app using Flutter & Riverpod

1. Initialize Dart Server

We will follow a mono repo approach such that we will have our backend and frontend under the same folder. For now, let’s create the backend folder structure with empty pubspec.yaml and main.dart files.

|____furni
| |____backend
| | |____api
| | | |____pubspec.yaml
| | | |____lib
| | | | |____main.dart

Let’s construct pubspec.yaml and main.dart.

name: furni_api
description: >-
  Server package for furni app
environment:
  sdk: ">=2.19.0 <3.0.0" # dart sdk constraints
void main() {
  print('Hello, Furni 👋');
}
dart run main.dart
Hello, Furni 👋

2. Using alfred

Now that we created our basic files and made sure everything is setup correctly, let’s add alfred to our project

dart pub add alfred

Initialize an Alfred instance. This will act as a single server app object. Also, let’s add a placeholder endpoint that we are going to change later just to check everything is working as expected.

import 'package:alfred/alfred.dart';

void main() {

  final app = Alfred();

  app.get('/hello', (req, res) {
    res.json({'message': 'Hello, Furni 👋'});
  });

  app.listen(4242);
}

Start the server

dart run main.dart
2023-04-28 02:41:36.204485 - info - HTTP Server listening on port 4242

Hit our placeholder endpoint

curl localhost:4242/hello
{"message":"Hello, Furni 👋"}

3. Add healthcheck endpoint

Nice, now let’s refactor our placeholder endpoint to a healthcheck endpoint to see how alfred manages callback and handlers.

Let’s add handlers and internals folder handlers - this is going to house all of our endpoint callbacks internals - will house all the internals needed for the server app, for now, we will just add the data folder which will contain all our app models.

|____furni
| |____backend
| | |____api
| | | |____pubspec.lock
| | | |____pubspec.yaml
| | | |____lib
| | | | |____main.dart
| | | | |____internals
| | | | | |____data
| | | | | | |____furniture.dart
| | | | |____handlers
| | | | | |____handlers.dart

Inside handlers.dart let’s define a new function called healthcheckHandler that satisfies the callback signature FutureOr<dynamic> Function(HttpRequest, HttpResponse) provided by alfred.

import 'dart:async';

import 'package:alfred/alfred.dart';

FutureOr<dynamic> healthcheckHandler(HttpRequest req, HttpResponse resp) {
  resp.statusCode = 200; // set the status code
  return {'status': 'available'};
}

Now let’s change our placeholder endpoint to use the healthcheckHandler we just created.

import 'package:alfred/alfred.dart';
import 'package:furni_api/handlers/handlers.dart';

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

  app.get('/healthcheck', healthcheck); // healthcheck handler will be created later

  app.listen(4242);
}

Now let’s run the app using dart run main.dart and then try to hit our new healthcheck endpoint.

curl -i localhost:4242/healthcheck
HTTP/1.1 200 OK
content-type: application/json; charset=utf-8
x-frame-options: SAMEORIGIN
x-xss-protection: 1; mode=block
transfer-encoding: chunked
x-content-type-options: nosniff

{"status":"available"}

Okay great, now let’s move on to implement our data.dartto define our application models which will be inside theinternals/data` we created earlier.

4. Creating our data models

furni app will consist of two screens:

  1. List of products with the ability to filter them based on their type.
  2. Details page of a selected product.

So let’s implment our data models with that in mind.


/// Data model to view details on multiple furniture items
///
class FurnitureType {
  final int id;
  String name;
  FurnitureType({
    required this.id,
    required this.name,
  });
}

/// Data model to view details on multiple furniture items
///
class FurnitureListItem {
  final int id;
  int type; // references FurnitureType.id
  String name;
  double price;
  String imageUrl;

  FurnitureListItem({
    required this.id,
    required this.type,
    required this.name,
    required this.price,
    required this.imageUrl,
  });

  factory FurnitureListItem.fromJson(Map<String, dynamic> json) {
    return FurnitureListItem(
      id: json['id'],
      name: json['name'],
      type: json['type'],
      price: json['price'],
      imageUrl: json['imageUrl'],
    );
  }

  Map<String, dynamic> toJson() {
    return {
      'type': type,
      'name': name,
      'price': price,
      'imageUrl': imageUrl,
    };
  }
}

/// Data model to view details on multiple furniture items
///
class FurnitureItemDetails {
  final int id;
  int type; // references FurnitureType.id
  String name;
  double price;
  String style;
  double height;
  double width;
  double depth;
  String imageUrl;

  FurnitureItemDetails({
    required this.id,
    required this.type,
    required this.name,
    required this.price,
    required this.style,
    required this.height,
    required this.width,
    required this.depth,
    required this.imageUrl,
  });

  factory FurnitureItemDetails.fromJson(Map<String, dynamic> json) {
    return FurnitureItemDetails(
      id: json['id'],
      name: json['name'],
      type: json['type'],
      price: json['price'],
      style: json['style'],
      height: json['height'],
      width: json['width'],
      depth: json['depth'],
      imageUrl: json['imageUrl'],
    );
  }

  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'type': type,
      'name': name,
      'price': price,
      'style': style,
      'height': height,
      'width': width,
      'depth': depth,
      'imageUrl': imageUrl,
    };
  }
}

Maybe we can explore adding a real database to the project in the future. But for now, to keep focusing on the server part, we will be loading our data from a local JSON. Let’s create a new mock.json file under backend/db

|____backend
| |____db
| | |____mock.json
| |____api
| | |____pubspec.lock
| | |____pubspec.yaml
| | |____lib
| | | |____main.dart
| | | |____internals
| | | | |____data
| | | | | |____furniture.dart
| | | |____handlers
| | | | |____handlers.dart

View the content of mock.json here

Great now in data/furniture.dart let’s read and serialize our fake data into our models.

5. Data serialization

We currently need to be able to serve the entire list of products and the details of a specific product so we will construct this as follows.

Notice in the following code Directory.current.path is relative to where you running dart run.

class MockDB {
  late Map<String, dynamic> parsedJson;
  MockDB() {
    final jsonString =
        File('${Directory.current.path}/../../db/mock.json')
            .readAsStringSync();

    parsedJson = JsonDecoder().convert(jsonString);
  }

  FurnitureItemDetails getFurnitureItemDetailss(int id) {
    return FurnitureItemDetails.fromJson((parsedJson['funritureList'] as List)
        .firstWhere((item) => item['id'] == id));
  }

  List<FurnitureListItem> getAllFurnitures() {
    final result = (parsedJson['funritureList'] as List).map((item) {
      return FurnitureListItem.fromJson(item);
    }).toList();
    return result;
  }
}

6. Finalize

Let’s test what we have done so far, so in the main function let’s try to pull data directly from a MockDB instance

import 'package:alfred/alfred.dart';
import 'package:furni_api/handlers/handlers.dart';
import 'package:furni_api/internals/data/furniture.dart';

void main() {
  final db = MockDB();
  print(db.getAllFurnitures());
  print(db.getFurnitureItemDetailss(1));
  final app = Alfred();

  app.get('/healthcheck', healthcheck);

  app.listen(4242);
}

Run the server and verify that all is working as expected

dart run main
[Instance of 'FurnitureListItem', Instance of 'FurnitureListItem', Instance of 'FurnitureListItem', Instance of 'FurnitureListItem', Instance of 'FurnitureListItem', Instance of 'FurnitureListItem', Instance of 'FurnitureListItem', Instance of 'FurnitureListItem', Instance of 'FurnitureListItem', Instance of 'FurnitureListItem', Instance of 'FurnitureListItem', Instance of 'FurnitureListItem', Instance of 'FurnitureListItem', Instance of 'FurnitureListItem', Instance of 'FurnitureListItem', Instance of 'FurnitureListItem', Instance of 'FurnitureListItem', Instance of 'FurnitureListItem', Instance of 'FurnitureListItem', Instance of 'FurnitureListItem']
Instance of 'FurnitureItemDetails'
2023-04-28 22:53:20.710610 - info - HTTP Server listening on port 4242

6. Conclusion and next steps

That is it for the first article in this series, so far we have:

  1. Started an alfred server
  2. Understood how handlers work in alfred
  3. Implemented our data models and generated mocked data

In the next article, we will be covering the following:

  1. Create an endpoint to get all the products
  2. Create an endpoint to get details about a specific product
  3. Error handling for potential bad requests sent by the client

Hope you had a good read with this one, have a great time, and happy baking!

Show your support for this series by giving us a ⭐️ on Github.

You can reach me at [email protected]