bones_api 1.0.11
bones_api: ^1.0.11 copied to clipboard

Bones_API - A Powerful API backend framework for Dart. Comes with a built-in HTTP Server, routes handler, entity handler, SQL translator, and DB adapters.

Bones_API #

pub package Null Safety Codecov CI GitHub Tag New Commits Last Commits Pull Requests Code size License

Bones_API - A Powerful API backend framework for Dart. Comes with a built-in HTTP Server, routes handler, entity handler, SQL translator, and DB adapters.

Usage #

A simple usage example:

import 'package:bones_api/bones_api.dart';
import 'package:bones_api/src/bones_api_server.dart';

void main() async {
  var api = MyAPI();

  // Calling the API directly:
  var r1 = await api.call(APIRequest.get('/service/base/foo'));
  print(r1);
  
  // Serving the API trough a HTTP Server:
  var apiServer = APIServer(api, '*', 8088);
  
  await apiServer.start();

  print('Running: $apiServer');
  print('URL: ${apiServer.url}');
}

class MyAPI extends APIRoot {
  MyAPI() : super('example', '1.0');

  @override
  Set<APIModule> loadModules() => {MyBaseModule()};
}

class MyBaseModule extends APIModule {
  MyBaseModule() : super('base');

  @override
  String? get defaultRouteName => '404';

  @override
  void configure() {
    routes.get('foo', (request) => APIResponse.ok('Hi[GET]!'));
    routes.post(
            'foo', (request) => APIResponse.ok('Hi[POST]! ${request.parameters}'));

    routes.any('time', (request) => APIResponse.ok(DateTime.now()));

    routes.any('404',
                    (request) => APIResponse.notFound(payload: '404: ${request.path}'));
  }
}

CLI #

You can use the built-in command-line interface (CLI) bones_api.

To activate it globally:

 $> dart pub global activate bones_api

Now you can use the CLI directly:

  $> bones_api --help

To serve an API project:

  $> bones_api serve --directory path/to/project --class MyAPIRoot

Hot Reload #

APIServer supports Hot Reload when the Dart VM is running with --enable-vm-service:

void main() async {
  var apiServer = APIServer(api, 'localhost', 8080, hotReload: true);
  await apiServer.start();
}

The CLI bones_api, when called with --hotreload, will launch a new Dart VM with --enable-vm-service (if needed) to allow Hot Reload.

To serve an API project with Hot Reload enabled:

  $> bones_api serve --directory path/to/project --class MyAPIRoot --hotreload

Using Reflection #

You can use the package reflection_factory to automate some declarations.

For example, you can map all routes in a class with one line of code:

File: module_account.dart:

import 'package:bones_api/bones_api.dart';
import 'package:reflection_factory/reflection_factory.dart';

// See Repositories sections below in this README:
import 'repositories.dart';

// The generated reflection code by `reflection_factory`:
part 'module_account.reflection.g.dart';

@EnableReflection()
class AccountModule extends APIModule {
  AccountModule(APIRoot apiRoot) : super(apiRoot, 'account');

  final AddressAPIRepository addressRepository = AddressAPIRepository();

  final AccountAPIRepository accountRepository = AccountAPIRepository();

  @override
  void configure() {
    // Maps the POST routes by reflection of any method in this class
    // that returns `APIResponse` or accepts `APIRequest`.
    routes.postFrom(reflection);
  }

  // The request parameters will be mapped to the correct
  // method parameter by name:
  Future<APIResponse> auth(String? email, String? password) async {
    if (email == null) {
      return APIResponse.error(error: 'Invalid parameters!');
    }

    if (password == null) {
      return APIResponse.unauthorized();
    }

    var sel = await accountRepository.selectAccountByEmail(email);

    if (sel.isEmpty) {
      return APIResponse.unauthorized();
    }

    var account = sel.first;

    // The object `account` will be automatically converted
    // to JSON when the response is sent through HTTP.
    return account.checkPassword(password)
        ? APIResponse.ok(account)
        : APIResponse.unauthorized();
  }
  
}

Declaring Entities & Reflection #

You can declare entities classes in portable Dart code (that also works in the Browser).

To easily enable toJSon and fromJson, just add @EnableReflection() to your entities.

File: entities.dart:

import 'dart:convert';

import 'package:crypto/crypto.dart';
import 'package:reflection_factory/reflection_factory.dart';

part 'entities.reflection.g.dart';

@EnableReflection()
class Account {
  int? id;

  String email;
  String passwordHash;
  Address? address;

  Account(this.email, String passwordOrHash, this.address, {this.id})
      : passwordHash = hashPassword(passwordOrHash);

  Account.create() : this('', '', null);

  bool checkPassword(String password) {
    return passwordHash == hashPassword(password);
  }

  static final RegExp _regExpHEX = RegExp(r'ˆ(?:[0-9a-fA-F]{2})+$');

  static bool isHashedPassword(String password) {
    return password.length == 64 && _regExpHEX.hasMatch(password);
  }

  static String hashPassword(String password) {
    if (isHashedPassword(password)) {
      return password;
    }

    var bytes = utf8.encode(password);
    var digest = sha256.convert(bytes);
    var hash = digest.toString();

    return hash;
  }
}

@EnableReflection()
class Address {
  int? id;

  String countryCode;
  String state;
  String city;
  String address1;
  String address2;

  String zipCode;

  Address(this.countryCode, this.state, this.city, this.address1, this.address2,
      this.zipCode,
      {this.id});

  Address.create() : this('', '', '', '', '', '');
}

See reflection_factory for more Reflection documentation.

Repositories & Database #

To stored entities in Databases and manipulate them you can set up an EntityRepositoryProvider:

File: repositories.dart

import 'package:bones_api/bones_api.dart';

// The PostgreSQL Adapter:
import 'package:bones_api/bones_api_adapter_postgre.dart';

// The above entities file:
import 'entities.dart';

// The `EntityRepository` provider:
class APIEntityRepositoryProvider extends EntityRepositoryProvider {
  static final APIEntityRepositoryProvider _instance =
      APIEntityRepositoryProvider._();

  // Singleton:
  factory APIEntityRepositoryProvider() => _instance;

  // Returns the current `APIRoot`:
  APIRoot? get apiRoot => APIRoot.get();

  APIEntityRepositoryProvider._() {
    // The current APIConfig:
    var apiConfig = apiRoot?.apiConfig;

    var postgreAdapter = PostgreSQLAdapter.fromConfig(
      apiConfig?['postgres'], // The connection configuration
      parentRepositoryProvider: this,
    );

    // Join the `PostgreSQLAdapter` and the Address/Account
    // `EntityHandler` (from reflection) to set up an
    // `EntityRepository` that uses SQL:
    
    // Entity `Address` in table `address`:
    SQLEntityRepository<Address>(
        postgreAdapter, 'address', Address$reflection().entityHandler);

    // Entity `Account` in table `account`:
    SQLEntityRepository<Account>(
        postgreAdapter, 'account', Account$reflection().entityHandler);
  }
}

// The Address repository:
class AddressAPIRepository extends APIRepository<Address> {
  AddressAPIRepository() : super(provider: APIEntityRepositoryProvider());

  // Selects an Address by field `state`:
  FutureOr<Iterable<Address>> selectByState(String state) {
    return selectByQuery(' state == ? ', parameters: {'state': state});
  }
}

// The Account repository:
class AccountAPIRepository extends APIRepository<Account> {
  AccountAPIRepository() : super(provider: APIEntityRepositoryProvider());

  // Selects an Account by field `email`:
  FutureOr<Iterable<Account>> selectAccountByEmail(String email) {
    return selectByQuery(' email == ? ', parameters: {'email': email});
  }

  // Selects an Account by field `address` and sub-field `state`:
  FutureOr<Iterable<Account>> selectAccountByAddressState(String state) {
    // This condition will be translated to a SQL with INNER JOIN (when using an SQLAdapter):
    return selectByQuery(' address.state == ? ', parameters: [state]);
  }
}

The config file used above:

File: api-local.yaml

postgres:
  database: yourdb
  username: postgres
  password: 123456

Bones_UI #

See also the package Bones_UI, a simple and easy Web User Interface Framework for Dart.

Features and bugs #

Please file feature requests and bugs at the issue tracker.

Contribution #

Any help from the open-source community is always welcome and needed:

  • Found an issue?
    • Please fill a bug report with details.
  • Wish a feature?
    • Open a feature request with use cases.
  • Are you using and liking the project?
    • Promote the project: create an article, do a post or make a donation.
  • Are you a developer?
    • Fix a bug and send a pull request.
    • Implement a new feature.
    • Improve the Unit Tests.
  • Have you already helped in any way?
    • Many thanks from me, the contributors and everybody that uses this project!

Author #

Graciliano M. Passos: gmpassos@GitHub.

License #

Artistic License - Version 2.0

5
likes
130
pub points
51%
popularity

Bones_API - A Powerful API backend framework for Dart. Comes with a built-in HTTP Server, routes handler, entity handler, SQL translator, and DB adapters.

Repository (GitHub)
View/report issues

Documentation

API reference

License

Artistic-2.0 (LICENSE)

Dependencies

args, async_extension, collection, dart_spawner, hotreloader, logging, mercury_client, mime, petitparser, postgres, reflection_factory, shelf, yaml, yaml_writer

More

Packages that depend on bones_api