# 🚀 Building a Fullstack App with dart_frog and Flutter in a Monorepo - Part 3

In the previous part, we set up models, data sources, repositories, and failures for the full-stack to-do application. In this part, we will:

* Make changes to the failure classes
    
* Create new failures
    
    * [`RequestFailure`](https://github.com/saileshbro/full_stack_todo_dart/blob/780b4662d4a03c17aaef281b100c8a686d8317bb/failures/lib/src/request_failure/request_failure.dart)
        
    * [`ValidationFailure`](https://github.com/saileshbro/full_stack_todo_dart/blob/780b4662d4a03c17aaef281b100c8a686d8317bb/failures/lib/src/validation_failure/validation_failure.dart)
        
    * [`ServerFailure`](https://github.com/saileshbro/full_stack_todo_dart/blob/780b4662d4a03c17aaef281b100c8a686d8317bb/failures/lib/src/server_failure/server_failure.dart)
        
* Create new Exceptions
    
    * [`ServerException`](https://github.com/saileshbro/full_stack_todo_dart/blob/780b4662d4a03c17aaef281b100c8a686d8317bb/exceptions/lib/src/server_exception/server_exception.dart)
        
    * [`HttpException`](https://github.com/saileshbro/full_stack_todo_dart/blob/780b4662d4a03c17aaef281b100c8a686d8317bb/exceptions/lib/src/http_exception/http_exception.dart)
        
    * [`NotFoundException`](https://github.com/saileshbro/full_stack_todo_dart/blob/780b4662d4a03c17aaef281b100c8a686d8317bb/exceptions/lib/src/http_exception/not_found_exception.dart)
        
    * [`BadRequestException`](https://github.com/saileshbro/full_stack_todo_dart/blob/780b4662d4a03c17aaef281b100c8a686d8317bb/exceptions/lib/src/http_exception/bad_request_exception.dart)
        
* Add validation to DTOs
    
* [Add JSON converters](https://github.com/saileshbro/full_stack_todo_dart/blob/780b4662d4a03c17aaef281b100c8a686d8317bb/models/lib/src/serializers/date_time_converter.dart)
    

# Creating and updating packages 📦

In this section, we will create new packages using [very\_good\_cli](https://pub.dev/packages/very_good_cli) and update existing ones to efficiently manage the packages in our full-stack to-do application.

## Working with Failure classes

> It's time to shake things up in the failure department! 💥 Let's get to work on updating our failure classes.

## Create `build.yaml`

We will now create a new file called [`build.yaml`](https://github.com/saileshbro/full_stack_todo_dart/blob/780b4662d4a03c17aaef281b100c8a686d8317bb/failures/build.yaml) in the `failures` directory and add the following code. This will change the behaviour of the [`json_serializable`](https://pub.dev/packages/json_serializable) so that it generates JSON keys in `snake_case`.

```yaml
targets:
  $default:
    builders:
      json_serializable:
        options:
          any_map: false
          checked: false
          create_factory: true
          disallow_unrecognized_keys: false
          explicit_to_json: true
          field_rename: snake
          generic_argument_factories: false
          ignore_unannotated: false
          include_if_null: true
```

### Update `Failure`

Let's add a new getter to the `Failure` abstract class to get the status code of the failure. This will allow us to map the failure to the appropriate [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) in the controller.

```diff
abstract class Failure {
  String get message;
+ int get statusCode;
}
```

### Update `NetworkFailure`

We will update our failure package and add more failures to it. To do this, we will begin by renaming the `code` field in our `NetworkFailure` class to `statusCode`. This will make the field more meaningful and easier for our readers to understand.

```diff
class NetworkFailure extends Failure with _$NetworkFailure {
  /// {@macro network_failure}
  const factory NetworkFailure({
    required String message,
-   required int code,
+   required int statusCode,
    @Default([]) List<String> errors,
  }) = _NetworkFailure;
```

## Create new failures

> Time to add some more failure friends to our app! 🙌

We'll be creating [`RequestFailure`](https://github.com/saileshbro/full_stack_todo_dart/blob/780b4662d4a03c17aaef281b100c8a686d8317bb/failures/lib/src/request_failure/request_failure.dart), [`ServerFailure`](https://github.com/saileshbro/full_stack_todo_dart/blob/780b4662d4a03c17aaef281b100c8a686d8317bb/failures/lib/src/server_failure/server_failure.dart), and [`ValidationFailure`](https://github.com/saileshbro/full_stack_todo_dart/blob/780b4662d4a03c17aaef281b100c8a686d8317bb/failures/lib/src/validation_failure/validation_failure.dart) to help us map out any potential errors that may occur.

### `RequestFailure`

To create a `RequestFailure` class, we will create a new file in the `src` directory called `request_failure/request_failure.dart`.

```dart
import 'dart:io';

import 'package:failures/failures.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'request_failure.freezed.dart';

@freezed
class RequestFailure extends Failure with _$RequestFailure {
  const factory RequestFailure({
    required String message,
    @Default(HttpStatus.badRequest) int statusCode,
  }) = _RequestFailure;
}
```

We will create two new classes in the `src` directory, `ServerFailure` and `ValidationFailure`. The `ServerFailure` class will be used to represent errors that occur on the server side of our application, and the `ValidationFailure` class will be used to represent validation errors in our application.

### `ServerFailure`

```dart
import 'dart:io';

import 'package:failures/failures.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'server_failure.freezed.dart';

@freezed
class ServerFailure extends Failure with _$ServerFailure {
  const factory ServerFailure({
    required String message,
    @Default(HttpStatus.internalServerError) int statusCode,
  }) = _ServerFailure;
}
```

### `ValidationFailure`

```dart
import 'dart:io';

import 'package:failures/failures.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'validation_failure.freezed.dart';

@freezed
class ValidationFailure extends Failure with _$ValidationFailure {
  const factory ValidationFailure({
    required String message,
    @Default(HttpStatus.badRequest) int statusCode,
    @Default({}) Map<String, List<String>> errors,
  }) = _ValidationFailure;
}
```

> Don't forget to export and run [`build_runner`](https://pub.dev/packages/build_runner) 😎

```dart
library failures;

export 'src/failure.dart';
export 'src/network_failure/network_failure.dart';
export 'src/request_failure/request_failure.dart';
export 'src/server_failure/server_failure.dart';
export 'src/validation_failure/validation_failure.dart';
```

> 💡 Note: You can run the `build_runner` command by running `flutter pub run build_runner build` in the terminal.

## Working with packages

> Get ready for some package updating fun! 📦

### Update `typedefs` package

We will be creating a function called `mapTodoId` in the `typedefs` package. This function will be responsible for converting a string id to a `TodoId` object. This is necessary because we need a way to convert user input into a format our application can understand and use. If the id is not valid, the `mapTodoId` function will return a `RequestFailure` object. This will allow us to handle any errors or invalid input gracefully, ensuring that our application is robust and can handle any potential issues

```dart
Either<Failure, TodoId> mapTodoId(String id) {
  try {
    final todoId = int.tryParse(id);
    if (todoId == null) throw const BadRequestException(message: 'Invalid id');
    return Right(todoId);
  } on BadRequestException catch (e) {
    return Left(
      RequestFailure(
        message: e.message,
        statusCode: e.statusCode,
      ),
    );
  }
}
```

The `mapTodoId` method will return either a `TodoId` object or a `RequestFailure` object. Make sure to add the dependencies [`either_dart`](https://pub.dev/packages/either_dart) and `failures` to the `pubspec.yaml` file of the `typedefs` package.

```yaml
dependencies:
  either_dart: ^0.3.0
  exceptions:
    path: ../exceptions
  failures:
    path: ../failures
```

### Create a new `exceptions` package

To handle internal exceptions and map them to the appropriate failures, we will create a new package called `exceptions` and throw our custom exceptions. For example, if we encounter a [`PostgreSQLException`](https://pub.dev/documentation/postgres/latest/postgres/PostgreSQLException-class.html) while inserting a new to-do, we will throw a `ServerException` and map it to the `ServerFailure` class. To create the `exceptions` package, run the following command in the terminal:

```bash
very_good create -t dart_pkg exceptions
```

This package will include different types of exceptions such as `ServerException`, `HttpException`, and `NotFoundException`. The `ServerException` will be thrown in the case of an internal server error, while the `HttpException` is an abstract class that will be extended by other exceptions like `NotFoundException` and `BadRequestException`. We can use these custom exceptions to handle internal errors and map them to the appropriate failure classes, such as `ServerFailure` or `RequestFailure`. We will start by creating a new file inside `src/server_exception/server_exception.dart` and add the following code:

#### `ServerException`

To create `ServerException` we will create a new file `src/server_exception/server_exception.dart` and add the following code:

```dart
class ServerException implements Exception {
  const ServerException(this.message);

  final String message;
  @override
  String toString() => 'ServerException: $message';
}
```

#### `HttpExpection`

To create `HttpException` we will create a new file `src/http_exception/http_exception.dart` and add the following code:

```dart
abstract class HttpException implements Exception {
  const HttpException(this.message, this.statusCode);
  final String message;
  final int statusCode;

  @override
  String toString() => '$runtimeType: $message';
}
```

#### `NotFoundException`

Next, we will create `NotFoundException`, which will be thrown when a resource is not found. To do this, create a new file called `src/http_exception/not_found_exception.dart` and add the following code:

```dart
class NotFoundException extends HttpException {
  const NotFoundException(String message) : super(message, HttpStatus.notFound);
}
```

#### `BadRequestException`

Similarly, we will create a new file inside `src/http_exception/bad_request_exception.dart` and add the following code:

```dart
class BadRequestException extends HttpException {
  const BadRequestException({
    required String message,
    this.errors = const {},
  }) : super(message, HttpStatus.badRequest);

  final Map<String, List<String>> errors;
}
```

Make sure to import the `HttpException`. Once you are done with `HttpException`, add an export statement in `http_exception.dart` file.

```dart
export './bad_request_exception.dart';
export './not_found_exception.dart';
```

And finally, export the `HttpException` from `exceptions/lib/exceptions.dart` file.

```dart
library exceptions;

export 'src/http_exception/http_exception.dart';
export 'src/server_exception/server_exception.dart';
```

### Update `models` package

#### Let's handle the validation

> Ready to add some sass to your data validation? Let's get those DTOs in shape! 💪

To add validation to our `CreateTodoDto` class, we will create a new static method called validated in `models/lib/src/create_todo_dto/create_todo_dto.dart`. This method will return either a `ValidationFailure` object or a `CreateTodoDto` object. We will use this method to ensure that our to-do creation requests contain all necessary information before they are processed. The validation rules are:

* the `title` should not be empty
    
* the `description` should not be empty
    

Before we can add the validated method to the `CreateTodoDto` class, we need to make sure that the necessary packages are added to the `pubspec.yaml` file.

```yaml
dependencies:
  either_dart: ^0.3.0
  exceptions:
    path: ../exceptions
  failures:
    path: ../failures
  freezed_annotation: ^2.2.0
  json_annotation: ^4.7.0
  typedefs:
    path: ../typedefs
```

Now we will create a `validated` method inside `CreateTodoDto`

```dart
  static Either<ValidationFailure, CreateTodoDto> validated(
    Map<String, dynamic> json,
  ) {
    try {
      final errors = <String, List<String>>{};
      if (json['title'] == null) {
        errors['title'] = ['Title is required'];
      }
      if (json['description'] == null) {
        errors['description'] = ['Description is required'];
      }
      if (errors.isEmpty) return Right(CreateTodoDto.fromJson(json));
      throw BadRequestException(
        message: 'Validation failed',
        errors: errors,
      );
    } on BadRequestException catch (e) {
      return Left(
        ValidationFailure(
          message: e.message,
          errors: e.errors,
          statusCode: e.statusCode,
        ),
      );
    }
  }
```

Similarly, we will create a new static method called `validated` to validate the `UpdateTodoDto`. We will ensure that at least one field is present.

```dart
  static Either<ValidationFailure, UpdateTodoDto> validated(
    Map<String, dynamic> json,
  ) {
    try {
      final errors = <String, List<String>>{};
      if (json['title'] == null || json['title'] == '') {
        errors['title'] = ['At least one field must be provided'];
      }
      if (json['description'] == null || json['description'] == '') {
        errors['description'] = ['At least one field must be provided'];
      }
      if (json['completed'] == null) {
        errors['completed'] = ['At least one field must be provided'];
      }
      if (errors.length < 3) return Right(UpdateTodoDto.fromJson(json));
      throw BadRequestException(
        message: 'Validation failed',
        errors: errors,
      );
    } on BadRequestException catch (e) {
      return Left(
        ValidationFailure(
          message: e.message,
          statusCode: e.statusCode,
          errors: e.errors,
        ),
      );
    }
  }
```

#### Custom JSON converters

To serialize and deserialize our `DateTime` objects, we will create a new file called `models/lib/src/serializers/date_time_serializer.dart`. In this file, we will add the necessary code to handle the serialization and deserialization of `DateTime` objects.

These classes will implement the [`JsonConverter`](https://pub.dev/documentation/json_annotation/latest/json_annotation/JsonConverter-class.html) interface and provide the necessary logic to convert `DateTime` objects to and from JSON. The `DateTimeConverterNullable` class will handle cases where the `DateTime` object may be `null`, while the `DateTimeConverter` class will handle non-null `DateTime` objects. With these classes in place, we will be able to correctly handle the formatting of `DateTime` objects when retrieving data from the database.

```dart
class DateTimeConverterNullable extends JsonConverter<DateTime?, dynamic> {
  const DateTimeConverterNullable();

  @override
  DateTime? fromJson(dynamic json) {
    if (json == null) return null;
    return const DateTimeConverter().fromJson(json);
  }

  @override
  String? toJson(DateTime? object) {
    if (object == null) return null;
    return const DateTimeConverter().toJson(object);
  }
}
class DateTimeConverter extends JsonConverter<DateTime, dynamic> {
  const DateTimeConverter();

  @override
  DateTime fromJson(dynamic json) {
    if (json is DateTime) return json;
    return DateTime.parse(json as String);
  }

  @override
  String toJson(DateTime object) {
    return object.toIso8601String();
  }
}
```

Now we can use this converter in our `Todo` model.

```diff
  factory Todo({
    required TodoId id,
    required String title,
    @Default('') String description,
    @Default(false) bool? completed,
-   required DateTime createdAt,
+   @DateTimeConverter() required DateTime createdAt,
-   DateTime? updatedAt,
+   @DateTimeConverterNullable() DateTime? updatedAt,
  }) = _Todo;
```

Once you have finished updating the `Todo` model to use the converters.

> Don't forget to run `build_runner` 😎

Woo hoo! We made it to the end of part 3 🎉

In this part, we gave our failure classes a little update and created some new ones. We also added some shiny new exceptions and made sure our DTOs were properly validated. We're almost there, friends! Just a few more steps until we can fully implement the CRUD operations for our awesome to-do app.

In the final part, we'll finally be able to connect to a Postgres database and complete all our backend routes. It's going to be a coding party 🎉 Are you ready to join the fun? I know I am! 🤩

And if you ever need a reference, just head on over to the GitHub repo for this tutorial at [https://github.com/saileshbro/full\_stack\_todo\_dart](https://github.com/saileshbro/full_stack_todo_dart).

Let's get ready to code some magic in part 4! 😄
