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:
- Restful Dart server using alfred
- Client app using Flutter & Riverpod
Other articles in this series:
-
Part One <--- you are here
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 the
internals/data` we created earlier.
4. Creating our data models
furni app will consist of two screens:
- List of products with the ability to filter them based on their type.
- 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:
- Started an alfred server
- Understood how handlers work in alfred
- Implemented our data models and generated mocked data
In the next article, we will be covering the following:
- Create an endpoint to get all the products
- Create an endpoint to get details about a specific product
- 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]