Build APIs for various HTTP Methods in Dart
Tags: flutter, dart, api, server
Published: 2021-11-15 17:59:21

Hello everyone!

In my previous article i talked about building APIs with Dart using the [shelf](https://pub.dev/packages/shelf) package, and how you can host it on Heroku.
If you haven't read that, do read it if you are new to building APIs in Dart.

![Poster of my previous article](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m9yzhv717zqimbuo3ote.png)
You can access the article from [here](https://dev.to/infiniteoverflow/create-an-api-with-dart-heroku-477k)

In this article, i am gonna talk about how you can implement various http methods (GET, POST, DELETE) using an add-on to the shelf package, which is the [shelf-router](https://pub.dev/packages/shelf_router) package.

![FRIENDS Excitement Scene](https://i.pinimg.com/originals/b3/75/8d/b3758db9d70f809d03bf7a92d188945b.gif)

A small limitation to the `shelf` package is that it dosen't have support for creating separate handlers for different request methods/ request URLs built into it. 
**For eg:** 
The various request methods can be a GET/POST/DELETE/PUT request using different URLs, or these requests can be on the same URL as well. But we have to explicitly write switch or myriad of if conditions to redirect to its appropriate handlers for further request handling.

But the `shelf_router` package defines a `Router` object which we can use to define the url and http method that is requesting the service and implicitly transfer control to appropriate handler functions.

Now, it sounds interesting right! So, without any further delay lets dive right in.

_**Note:** I will be using IntelliJ as the IDE throughout this article, but you can replicate the same in VsCode or Android Studio._

You can skip the following section if you know how to create a new `dart-server` project.

---
## Create a new dart-server project

Click on File->New Project

![Pic of New Project Screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p9k5a9k0a719g1s46mxs.png)

Name it anything you want!

![Enter the name of the project](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yp5vap1vxhzuluy5rj60.png)

After these two steps, you should get the template for your `dart-server` ready!

---

Now, head over to **lib->server.dart**. Your 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}"');
```
This is the approach using the `shelf` package.

Now, lets add the [shelf-router](https://pub.dev/packages/shelf_router) package to our `pubspec.yaml` file. 
```yaml
shelf_router: ^1.1.2
```
_This is the version of the package at the time of writing this article. Please update it accordingly._

Next, lets create a new dart file and name it `api.dart`. We will write all the routing logic in this file.

Add imports to the `shelf` and `shelf_router` package as follows:

```dart
import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart';
```

Next, create a new class `Api` and write a getter method `handler` that returns a `Handler` object.

```dart
class Api {
  Handler get handler {
    final router = Router();

    // Write your logic here
    
    return router;
  }
}
```

Now, go back to the `server.dart` file and update the last-second line of the main function as follows: 

```dart
var server = await io.serve(Api().handler, _hostname, port);
```
_You may have to import the api.dart file as well._

Now, your server will serve Requests from this new API Class handler rather than from the typical `_echoRequest()` function.

Now, lets get back to the `api.dart` file. All the codes thats gonna follow will be written inside the `handler` method below the comment.

## GET Request

First, lets see how a get request for the url `/api/<name>` will work, where `<name>` can be any word:

```dart
router.get('/api/<name>', (Request request,String name) {
    return Response.ok(
        jsonEncode({
          'success':true,
          'data':name
        }),
        headers: {
          'Content-type':'application/json'
        },
    );
});
```

Here, we have defined a handler method to process in case a GET request is made to the url `/api/<name>`, where name could be any word.
The Handler method will return an object of type `Response` with OK status code. The response will be a JSON String as follows:
![Output image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u90jx7g3amwgp20s4wwr.png)

Since we have added a placeholder in the url parameter of `router.get()`, ie `<name>`, the handler method will take a second parameter that will contain the value passed in the url, which is in-turn returned as Response. 

For the remaining part of this article i am going to use a local json file which has pre-filled data. 
You can access the file from [here](https://github.com/infiniteoverflow/http_methods_demo/blob/main/friends.json).

The file contents should look like this:

```json
{
  "users":[
    {
      "id":1,
      "Name":"Rachel Green",
      "Age": 32
    },
    {
      "id":2,
      "Name":"Ross Geller",
      "Age": 33
    },
    {
      "id":3,
      "Name":"Monica Geller",
      "Age": 32
    },
    {
      "id":4,
      "Name":"Joey Tribbiani",
      "Age": 31
    },
    {
      "id":5,
      "Name":"Chandler Bing",
      "Age": 33
    },
    {
      "id":6,
      "Name":"Pheobe Buffay",
      "Age": 34
    }
  ]
}
```

Now, inside the Api class we will define a variable `data` of type `List`, which will store the list of users from the json file.

```dart
List data = jsonDecode(
    File('friends.json').readAsStringSync()
  )['users'];
```

## POST Request

Now, lets see how we can perform a POST Request. Let's perform a add operation, where we will add new information from the POST payload.
**POST Payload** is the information you pass when hitting the API Endpoint using a POST Request.

```dart
router.post('/api/add', (Request request) async{
    final payload = jsonDecode(await request.readAsString());
    data.add(payload);

    return Response.ok(
      jsonEncode({
        'success':true,
        'data':data
      }),
      headers: {
        'Content-type':'application/json'
      },
    );
  }
);
```

I will be using **Postman** to perform the POST Operation on the API. You can try any API Testing tools of your choice.

I will be passing the following data as the payload(body) for the POST Request:

```json
{
    "Id": 7,
    "Name":"Aswin",
    "Age":22,
}
```

This json data will be stored in the variable `payload` in the handler function above, and the same is added to the `data` variable which is then displayed as a response to verify it is added correctly.

This is a basic application of how you can use the POST request. Some improvisations can be:

- As of now, we are just appending the new data to the variable `data` and not to the actual json file. That means, when you restart the server, the appended data will be gone as that time you are freshly fetching from the json file.
You can change the code to append the new data permanently to the json file.

- We are passing the hardcoded Id in the payload, you can improvise it by just passing the Name and Age and calculating its Id in the function by considering the Id of the last entry in the json data.

## DELETE Request

Finally, lets see how the DELETE Request works.

```dart
router.delete('/api/delete/<id>', (Request request, String id) {
      final idN = int.parse(id);
      final deletedData = data.firstWhere(
        (element) => element['id']==idN,
        orElse: ()=> null 
      );

      if(deletedData==null) {
        return Response.notFound(
          jsonEncode({
            'success':false,
            'data':'invalid id'
          }),
          headers: {
            'Content-type': 'application/json'
          },
        );
      }

      int pos = deletedData['id'];
      data.removeAt(pos-1);

      return Response.ok(
        jsonEncode({
          'success':true,
          'data':deletedData
        }),
        headers: {
          'Content-type':'application/json'
        }
      );
    });
```

URL : `api/delete/<id>`
We first get the id passed in the URL and store it in the variable `idN`. Next, we use that variable to search if any entry exists in the `data` variable with the given id.
If it dosen't exist `null` is returned and we return a Response object of status code 404(Not Found).

If Id exists, we remove the entry from the `data` variable and the deleted information is returned in the Response with a status code of 200(OK).

---

Well folks, thats it for now. You can access the above codes on [GitHub](https://github.com/infiniteoverflow/http_methods_demo).

### Where can you go from here

In this article you learned how you can read/add/delete from a database(here, a json file). 

- You can take it to the next level by connecting to a online database like Supabase, Firebase, Mongo, etc and make a production level API.

- You can also add a Authentication or Authorization check and allow only the requests that sends a Valid Bearer Token to perform the operations.


If you have any doubts while working with the codes in this article, 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/)


So, it's a good bye then. I will see you in my next article!

![Joey saying Bye-Bye](https://c.tenor.com/UOvfMdxpeWIAAAAC/friends-joey.gif)