In distributed apps such as the client-server pattern, you often want to work with the same model classes on both ends. Why? Because client input needs to be validated at the client side and for this, we need the model in the client app. Data has to pass through the model before being stored so that if we want to store the data in a client database (indexed_db
) as well as in a server data store, we need the model on both the sides.
You can see how common classes are created for client and server apps in the client_server_db
app. This is a to-do application; the client is started from web/app.html
and the server is started from bin/server.dart
. The client stores the to-do data in indexed_db
, while the server stores the data in memory. Both client and server need the model classes.
The project structure is shown in the following screenshot:
If you want to be able to import your common model library from another package, the files must be under the lib
directory. In this example, we see from pubspec.yaml
that the name of the app is client_server
. The model (with the Task
and Tasks
classes) has its own folder lib/model
, which contains the model classes defined in the shared_model
library within lib/shared_model.dart
. The client-app web/app.dart
uses indexed_db
; it therefore imports a library idb_client
to work with indexed_db
, as shown in the following code:
import 'package:client_server/idb_client.dart';
The idb_client
library, defined in lib/idb_client.dart
, imports the shared_ model
library, as shown in the following code:
library idb_client;
import 'package:client_server/shared_model.dart';
import 'dart:async';
import 'dart:html';
import 'dart:indexed_db';
import 'dart:convert';
part 'idb/idb.dart';
part 'view/view.dart';
In this way, the client app knows about the model. The server app also knows about the model by importing it (see bin/server.dart
):
import 'dart:io';
import 'dart:convert';
import 'package:client_server/shared_model.dart';
// rest of code
Here's another common use case for shared models; say the class has a method that performs HTTP requests; on the client, you will use HttpRequest
from the dart:html
library, while on the server, you will use the one from dart:io
instead. For security reasons, both dart:io
and dart:html
cannot be imported in the same library, so it's a common practice to define the shared class as an abstract class, and then delegate the concrete implementation of the HttpRequest
method to both the client and server classes, which extend the shared abstract class.