DEV Community

Cover image for Build APIs for various HTTP Methods in Dart
Aswin Gopinathan
Aswin Gopinathan

Posted on

Build APIs for various HTTP Methods in Dart

Hello everyone!

In my previous article i talked about building APIs with Dart using the 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
You can access the article from here

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 package.

FRIENDS Excitement Scene

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

Name it anything you want!

Enter the name of the project

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:

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}"');
Enter fullscreen mode Exit fullscreen mode

This is the approach using the shelf package.

Now, lets add the shelf-router package to our pubspec.yaml file.

shelf_router: ^1.1.2
Enter fullscreen mode Exit fullscreen mode

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:

import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart';
Enter fullscreen mode Exit fullscreen mode

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

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

    // Write your logic here

    return router;
  }
}
Enter fullscreen mode Exit fullscreen mode

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

var server = await io.serve(Api().handler, _hostname, port);
Enter fullscreen mode Exit fullscreen mode

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:

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

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

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.

The file contents should look like this:

{
  "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
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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.

List data = jsonDecode(
    File('friends.json').readAsStringSync()
  )['users'];
Enter fullscreen mode Exit fullscreen mode

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.

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'
      },
    );
  }
);
Enter fullscreen mode Exit fullscreen mode

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:

{
    "Id": 7,
    "Name":"Aswin",
    "Age":22,
}
Enter fullscreen mode Exit fullscreen mode

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.

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'
        }
      );
    });
Enter fullscreen mode Exit fullscreen mode

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.

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

LinkedIn : Aswin Gopinathan

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

Joey saying Bye-Bye

Discussion (0)