Create an API with Dart + Heroku
Tags: flutter, dart, heroku, api
Published: 2021-11-11 15:33:38

When you first started learning Flutter, you might have come across videos on **Working with APIs in Dart/Flutter** right?
But did you ever think about **How to build your own API with Dart** ?

API be like:

![doggy-gif](https://c.tenor.com/bgbgJ8tpK0AAAAAM/dog-crying.gif)

Worry not when i am here!

In this tutorial you are gonna learn just two things :

1. Create your own API with Dart
2. Host it and make it public using Heroku

But, we need some data which we can send through the API right? :thinking:

**Let's use <a href="https://supabase.io/" target="_blank">Supabase</a> !**

![Supabase logo](https://supabase.io/brand-assets/supabase-logo-wordmark--dark.svg)

So, i have created a Database table in Supabase with the following content : 

![Supabase DB image](https://github.com/infiniteoverflow/supabase_db/blob/main/db.png?raw=true)

Check out the [official documentation](https://supabase.io/docs/guides/database) to know how you can create and add rows to your database.

Now head over to your IDE and create a new Dart project and select **Dart->Web Server**. This article uses IntelliJ, but you can replicate the same steps in your IDE too!

![IntelliJ New Project Screen](https://github.com/infiniteoverflow/supabase_db/blob/main/ide.png?raw=true)

Enter an appropriate name and create the project. 

A `bin/server.dart` file will be created which contains our server code that will create our API.

<hr>
## Optional Section

This section is for those people who are using Visual Studio, chances are you may not get an option to create a Dart Server App with the above template.

No worries, you can follow the following steps :

**1)** Create a plain Dart Command-line App : 

```bash
dart create my_server
```
**2)** Rename the file inside `bin` folder to `server.dart`. You can use any name, i have used this name so that we are on the same page :P

**3)** Add the following dependencies in `pubspec.yaml` file :

```yaml
dependencies:
  args: ^2.0.0
  shelf: ^1.1.0
```

**4)** Add the code given in the section below to your newly created `server.dart` file.

<hr>

Your `server.dart` file should look like this :

```dart
import 'dart:io';

import 'package:args/args.dart';
import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf/shelf_io.dart' as io;

// For Google Cloud Run, set _hostname to '0.0.0.0'.
const _hostname = 'localhost';

void main(List<String> args) async {
  var parser = ArgParser()..addOption('port', abbr: 'p');
  var result = parser.parse(args);

  // For Google Cloud Run, we respect the PORT environment variable
  var portStr = result['port'] ?? Platform.environment['PORT'] ?? '8080';
  var port = int.tryParse(portStr);

  if (port == null) {
    stdout.writeln('Could not parse port value "$portStr" into a number.');
    // 64: command line usage error
    exitCode = 64;
    return;
  }

  var handler = const shelf.Pipeline()
      .addMiddleware(shelf.logRequests())
      .addHandler(_echoRequest);

  var server = await io.serve(handler, _hostname, port);
  print('Serving at http://${server.address.host}:${server.port}');
}

shelf.Response _echoRequest(shelf.Request request) =>
    shelf.Response.ok('Request for "${request.url}"');

```

I know you will be like :

![Mrs. Geller saying "That's a lot of information in 30 seconds"](https://y.yarn.co/bd8cc9c5-f4cc-4951-a4db-5a925b50c7f1_text.gif)

So, Let's break it down and understand each line.

1) Defining the Host Name :

```dart
const _hostname = 'localhost';
```

If you are debugging, then we use `localhost` for testing the application. But we have to change that once we plan to host it somewhere like Heroku or Cloud Run.  
We will be changing that at the end of this article where we will be hosting this API on Heroku.
  
2) `Line 11` and `Line 12` is for Argument Parser, if you wish to send the port number to use via Command Line Arguments.
eg:
```bash
 dart run bin/server.dart --port 8081
```

3) Define the port to be used programmatically :
```dart
 var portStr = result['port'] ?? Platform.environment['PORT'] ?? '8080';
```

If we haven't passed the port number through the CLI, we check if we are using any Hosting Service (since they have their own ports) and use the Port defined by its Environment Variable. Still if it returns false (ie, we are in debug mode) we use our custom port of choice (Here, 8080).

4) Handle Invalid Port Numbers :

```dart
if (port == null) {
    stdout.writeln('Could not parse port value "$portStr" into a number.');
    // 64: command line usage error
    exitCode = 64;
    return;
  }
```

5) Now the fun part! We define the Pipeline which the server has to use to receive requests and send responses to the caller.

```dart
var handler = const shelf.Pipeline()
      .addMiddleware(shelf.logRequests())
      .addHandler(_echoRequest);
```

We use a Middleware defined by the `shelf` package to log the requests incoming to the server, and we define a Handler method to handle the incoming requests.
The Handler takes appropriate action based on the request method and returns some data.

6) Serve the Server :

```dart
var server = await io.serve(handler, _hostname, port);
```

We attach the Pipeline, the Host Name, and the Port Number to the server and make it functional. Now, your Server is ready for action.

But wait! We have just one more thing left before we hit the RUN Button.  
We have to define the Handler Method.

```dart
shelf.Response _echoRequest(shelf.Request request) =>
    shelf.Response.ok('Request for "${request.url}"');
```

It returns an object of type `Response` and takes in an argument of type `Request`. 

Does this architecture ring a bell for you ? :bell:

Yes, its our normal Web Server Architecture where we send Requests from the browser and the server returns a Response.

So, every time you call this program (ie, by using GET/POST/DELETE etc), this particular method is called and further processing happens from here. That's why we have attached this method as a Handler in the Server Pipeline.

```dart
shelf.Response.ok('Request for "${request.url}"');
```

This line says that *"We have received your request and its perfect, so we are giving you a 200 Status Code with the data 'Request for ${request.url}'"*

![Server Response after GET](https://github.com/infiniteoverflow/supabase_db/blob/main/server_response.png?raw=true)

See that wasn't so bad right?

![Chandler Dancing](https://c.tenor.com/vyb-h7bL9-IAAAAM/chandler-dance.gif)

Now, lets connect our Supabase DB to our Dart Code.

We need to use the official Supabase Dart package :

https://pub.dev/packages/supabase

![Package details](https://github.com/infiniteoverflow/supabase_db/blob/main/package.png?raw=true)

Now, open `server.dart` and import the supabase package :

```dart
import 'package:supabase/supabase.dart';
```

Now, create a new method which will act as the Handler method when we call the url `/users` :

```dart
Future<shelf.Response> _echoUsers(shelf.Request request) async{
  final client = SupabaseClient('<SUPABASE URL>', '<SUPABASE KEY>');

  // Retrieve data from 'users' table
  final response =  await client
      .from('users')
      .select()
      .execute();

  var map = {
    'users' : response.data
  };

  return shelf.Response.ok(jsonEncode(map));
}
```

The **Supabase URL** and **Supabase Key** can be found from your Settings page in Supabase Dashboard.

![Pic of dashboard](https://github.com/infiniteoverflow/supabase_db/blob/main/dashboard.png?raw=true)

*`anon` `public` is the <b>Supabase Key<b>*

The table created in Supabase DB is named `users` and we have used the same table to select all rows in the table and return them into the variable `response`. The query returns a list of rows which is directly stored into the `users` key of the `map` variable, and then returned as response for the API call.

Now, we need to add the routing. Remember, in the last section i had mentioned that `_echoRequest()` method is called every time we hit the API. So, once we reach there, we need to route to the correct Handler method based on the API URL. ie, If we enter `/api` we should call the Handler method defined for that request. Similarly, here we are calling `/users`, we need to route to the method `_echoUsers`.

So, we change the code inside `_echoRequest` as follows :

```dart
Future<shelf.Response> _echoRequest(shelf.Request request) async{
  switch(request.url.toString()) {
    case 'users': return _echoUsers(request);
    default : return shelf.Response.ok('Invalid url');
  }
}
```

Now, every time you hit the url `/users`, it will call the `_echoUsers` method in which we pass the request object. 
If we try to access any other urls, a message "Invalid url" will be returned to the user.

But, i missed something! How does the code `request.url.toString()` work ?

Let me explain it with an example. Imagine you typed the following url in the browser : `http://localhost:8080/users`, `request.url.toString()` will return *'users'*.
Similarly, if you typed the url : `http://localhost:8080/users/aswin/profile`, `request.url.toString()` will return *'users/aswin/profile'*.

So, basically that snippet of code will return anything after `http://localhost:8080/`.

Well, now i see you have become an expert in building and deploying a full-fledged API in your local machine. But, trust me there are lot of Middleware packages available on pub.dev that extends the capability of the `shelf` package.

I will share more details at the end of this article. But before that, we have one more task left. Right?

Yes! Lets host this and make this API available to everyone!!

## Hosting with Heroku

This section will take just 5 mins of your time. And in the next 10 mins your API will be live!

1) Head over to the [Heroku](heroku.com) Website and login using your credentials.

2) Create a new app from your dashboard.
![Create new app button](https://github.com/infiniteoverflow/supabase_db/blob/main/dash.png?raw=true)
![Enter app name and select region](https://github.com/infiniteoverflow/supabase_db/blob/main/newapp.png?raw=true)

3) Your app dashboard should look like this :
![Dashboard of newly created app](https://github.com/infiniteoverflow/supabase_db/blob/main/app.png?raw=true)

4) Next, Download and install the [Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli)

5) From the CLI, login into your Heroku account if you haven't yet.

```bash
$ heroku login
```

6) Initialize a Git Repository. Type the following Commands while inside the root directory of the project.

```bash
$ cd my-project/
$ git init
$ heroku git:remote -a <heroku-app-name>
```

7) Now, we need to initialize the Build pack which will be used by heroku to launch the dart app.
Check out the build pack we are gonna use on [GitHub](https://github.com/igrigorik/heroku-buildpack-dart)

Type the following command :

```bash
$ heroku buildpacks:set https://github.com/igrigorik/heroku-buildpack-dart.git
```

8) Next, we need to set the Environment Variables in our Heroku Console. Head to **Dashboard->Settings->Config Vars-> Reveal Config Vars** and add the following key and value :

key : DART_SDK_URL
value : *Link to the Dart SDK zip file for Linux from the Official website*

At the time of writing this article, this was the link to the latest stable version of Dart SDK for Linux : https://storage.googleapis.com/dart-archive/channels/stable/release/2.14.4/sdk/dartsdk-linux-x64-release.zip

You can go to the [Official Dart SDK](https://dart.dev/get-dart/archive) site and search for the x64 package for Linux.

9) Next, head over to your root director of your project and create a new file named `Procfile` without any extensions.
Add the following content to the file : 

```
web: ./dart-sdk/bin/dart bin/server.dart
```

This will automatically run the `server.dart` code when you run the public link.

10) Now, its time to push the code to heroku!

```bash
$ git push heroku master
```

You will get the link to the public URL which you can use it to access the API and even share it with the community 💙

Hurray!!

![Joey and Chandler celebrating](https://c.tenor.com/1woz9afWCTUAAAAC/friends-birthday.gif)

You just created your own API and hosted it on Heroku. Give yourself a pat in the back!!

<hr>

If you are facing any issues anywhere, feel free to reach out to me on my handles : 

Twitter : [@GopinathanAswin](https://twitter.com/GopinathanAswin)

LinkedIn : [Aswin Gopinathan](https://www.linkedin.com/in/aswin-gopinathan-69556716a/)

<hr>

## Don't go yet!

You might be thinking, where you can go ahead from here right? 

Like i mentioned before, `shelf` is just a Web server Middleware, it requires a lot of other add-ons to make a Full-Fledged API. Some of the few add-ons are :

1) [Shelf Router](https://pub.dev/packages/shelf_router)

2) [Shelf Router Generator](https://pub.dev/packages/shelf_router_generator)

3) [Shelf Static](https://pub.dev/packages/shelf_static)

4) [Shelf WebSocket](https://pub.dev/packages/shelf_web_socket)

5) [Shelf CORS Headers](https://pub.dev/packages/shelf_cors_headers)

6) [Shelf Proxy](https://pub.dev/packages/shelf_proxy)

You can read more about these in Filip's Article : [Shelf — Web Server with Dart](https://dev.to/abarroso/shelf-backend-with-dart-42dj)

I guess its bye-bye time now :😔 , but i will be back soon with more articles as i learn something new!!

Happy coding!