🎉 New release for Pusher Chatkit - Webhooks! Extend your in-app chat functionality
Hide
Products
chatkit_full-logo

Extensible API for in-app chat

channels_full-logo

Build scalable realtime features

beams_full-logo

Programmatic push notifications

Developers

Docs

Read the docs to learn how to use our products

Tutorials

Explore our tutorials to build apps with Pusher products

Support

Reach out to our support team for help and advice

Sign in
Sign up

Becoming a backend developer - Part 2: Building the server

  • Suragch
March 22nd, 2019
You need experience in at least one of Android, iOS or Flutter development.

This is a guide for mobile app developers. In this three-part series we are covering all the basics of what it takes to become a backend developer.

Introduction

In the first tutorial we went over background knowledge related to client-server communication. This included URIs, HTTP, REST APIs, and JSON. If any of these topics are unfamiliar to you, go back and review them.

Now, let’s build a server!

Prerequisites

I'm not assuming you have any backend server experience, but I won't be repeating online documentation, so you will need to be able to following the installation directions from the links I give you.

I will be using Visual Studio Code to do the server programming. Feel free to use another IDE if you prefer.

Building the backend

For development purposes, we are going to install the server software on our local machine and have it communicate directly with our mobile app running in an emulator.

Below are two examples of a backend server: Node.js and Server-Side Dart. You only need to choose one. Node.js is very popular and you write the server code in JavaScript. Server Side Dart is not nearly as popular as Node.js, but for Flutter developers the advantage is that you can use the same language for the frontend and the backend. It really doesn't matter which one you choose, but if you can't decide, go with Node.js.

Backend example one: Node.js

Note: This tutorial was tested with Node.js 10.15.1

Install Node.js

Go to nodejs.org to download and install Node.js.

The Getting Started Guide shows the code for a basic HTTP server. It doesn’t explain the code, though, so I have added comments below:

    // The require() function loads modules in Node.js. 
    // A module is code that is in another file.
    // 'http' is a builtin module in Node.js. It allows data transfer
    // using the HTTP protocol and enables setting up an HTTP server.
    const http = require('http');

    // During development we will use the localhost.
    const hostname = '127.0.0.1';
    const port = 3000;

    // set up an http server
    // Two parameters are passed in:
    //     req = request (from the client)
    //     res = response (from the server)
    const server = http.createServer((req, res) => {

      // A status code of 200 means OK (A 404 would mean Not Found)
      res.statusCode = 200;

      // A header adds additional information. 
      // Here we are using a name-value pair to set the
      // media type (MIME type) as plain text (as opposed to html).
      res.setHeader('Content-Type', 'text/plain');

      // This writes a message and then ends the response.
      res.end('Hello World\n');
    });

    // This causes the server to listen for requests from clients on 
    // the hostname and port that we defined above.
    server.listen(port, hostname, () => {
      console.log(`Server running at http://${hostname}:${port}/`);
    });

Go ahead and test the code by running the app as described in the Getting Started Guide. You should see “Hello World” in the browser window when you navigate to http://localhost:3000.

Implement our API

In Part 1 [ADD LINK] I told you the REST API that we were going to make would look like this:

    GET http://localhost:3000/              // get all items
    GET http://localhost:3000/id            // get item at id
    POST http://localhost:3000/             // create new item
    PUT http://localhost:3000/id            // replace item at id
    PATCH http://localhost:3000/id          // update item at id
    DELETE http://localhost:3000/id         // delete item at id

Our client app that we will make in Part 3 is going to look like this:

So we need to make our server handle all of these requests. First go to the terminal and create a new directory for our Node.js project.

    mkdir nodejs_server
    cd nodejs_server

The way to create a new project is to use the Node Package Manager (npm). Run the following command and accept the default values for everything. (If you need to edit this info later you can open package.json.)

    npm init

We are also going to use the Express framework, which simplifies a lot of the HTTP protocol handling.

    npm install express --save

Now create the server file that we named in the npm init step above:

    touch index.js

Open it in an editor (I'm using VSCode), and paste in the following:

    // nodejs_server/index.js

    var express = require('express');
    var bodyParser = require('body-parser');
    var app = express();

    // bodyParser is a type of middleware
    // It helps convert JSON strings
    // the 'use' method assigns a middleware
    app.use(bodyParser.json({ type: 'application/json' }));

    const hostname = '127.0.0.1';
    const port = 3000;

    // http status codes
    const statusOK = 200;
    const statusNotFound = 404;

    // using an array to simulate a database for demonstration purposes
    var mockDatabase = [
        {
            fruit: "apple",
            color: "red"
        },
        {
            fruit: "banana",
            color: "yellow"
        }
    ]

    // Handle GET (all) request
    app.get('/', function(req, res) {
        // error checking
        if (mockDatabase.length < 1) {
            res.statusCode = statusNotFound;
            res.send('Item not found');
            return;
        }
        // send response
        res.statusCode = statusOK;
        res.send(mockDatabase);
    });

    // Handle GET (one) request
    app.get('/:id', function(req, res) {
        // error checking
        var id = req.params.id;
        if (id < 0 || id >= mockDatabase.length) {
            res.statusCode = statusNotFound;
            res.send('Item not found');
            return;
        }
        // send response
        res.statusCode = statusOK;
        res.send(mockDatabase[id]);
    });

    // Handle POST request
    app.post('/', function(req, res) {
        // get data from request
        var newObject = req.body; // TODO validate data
        mockDatabase.push(newObject);
        // send created item back with id included
        var id = mockDatabase.length - 1;
        res.statusCode = statusOK;
        res.send(`Item added with id ${id}`);
    });

    // Handle PUT request
    app.put('/:id', function(req, res) {
        // replace current object
        var id = req.params.id;     // TODO validate id
        var replacement = req.body; // TODO validate data
        mockDatabase[id] = replacement;
        // report back to the client
        res.statusCode = statusOK;
        res.send(`Item replaced at id ${id}`);
    });

    // Handle PATCH request 
    app.patch('/:id', function(req, res) {
        // update current object
        var id = req.params.id;        // TODO validate id
        var newColor = req.body.color; // TODO validate data
        mockDatabase[id].color = newColor;
        // report back to the client
        res.statusCode = statusOK;
        res.send(`Item updated at id ${id}`);
    });

    // Handle DELETE request 
    app.delete('/:id', function(req, res) {
        // delete specified item
        var id = req.params.id;  // TODO validate id
        mockDatabase.splice(id, 1);
        // send response back
        res.statusCode = statusOK;
        res.send(`Item deleted at id ${id}`);
    });

    app.listen(port, hostname, function () {
        console.log(`Listening at http://${hostname}:${port}/...`);
    });

Save the file and run it in the terminal.

    node index.js

The server is now running on your machine. You can use Postman (see docs and tutorial) to test the server now, or you can use one of the client apps that we will make in part three.

Further study

Backend example two: Server Side Dart

Note: This tutorial was tested with Dart 2.1.2

Install Dart

If you have Flutter installed on your system, then Dart is already installed. But if not, then go to this link to download and install the Dart SDK.

Check if dart/bin is in your system path:

    # Linux or Mac
    echo $PATH

    # Windows Command Prompt
    echo %path%

    # Windows Powershell
    $env:Path -split ';'

If it isn't and you just installed it from the link above (because you don't have Flutter), you can add it to your path like this:

    # Linux or Mac
    export PATH="$PATH:/usr/lib/dart/bin"

On Windows it is easiest to use the GUI to set environment variables.

If you already had Flutter/Dart installed, find your Flutter SDK directory. Then you can add the path like this (replacing <flutter>):

    export PATH="$PATH:<flutter>/bin/cache/dart-sdk/bin"

This only updates the path until you restart your machine. You will probably want to update your .bash_profile (or whatever you use on your system) to make it permanent.

Install Aqueduct

We are also going to use the Aqueduct framework to make HTTP request APIs easier to build. Now that we have Dart installed, we can install Aqueduct like this:

    pub global activate aqueduct

Follow the directions to add the $HOME/.pub-cache/bin to your path if you are instructed to.

Implement our API

In part one I told you the REST API that we were going to make would look like this:

    GET http://localhost:3000/              // get all items
    GET http://localhost:3000/id            // get item at id
    POST http://localhost:3000/             // create new item
    PUT http://localhost:3000/id            // replace item at id
    PATCH http://localhost:3000/id          // update item at id
    DELETE http://localhost:3000/id         // delete item at id

Our client app that we will make in part three is going to look like this:

So we need to make our server handle all of these requests.

First go to the terminal and cd to the directory that you want to create the server project folder in. Then type:

    aqueduct create dart_server

Open the project in an editor. The Aqueduct documentation recommends IntelliJ IDEA, but I am using Visual Studio Code with the Dart plugin.

Open the lib/channel.dart file and replace it with the following code:

    // dart_server/lib/channel.dart

    import 'package:dart_server/controller.dart';
    import 'dart_server.dart';

    // This class sets up the controller that will handle our HTTP requests
    class DartServerChannel extends ApplicationChannel {

      @override
      Future prepare() async {
        // auto generated code
        logger.onRecord.listen((rec) => print("$rec ${rec.error ?? ""} ${rec.stackTrace ?? ""}"));
      }

      @override
      Controller get entryPoint {
        final router = Router();

        // We are only setting up one route. 
        // We could add more below if we had them.
        // A route refers to the path portion of the URL.
        router
          .route('/[:id]') // the root path with an optional id variable
          .link(() => MyController()); // requests are forwarded to our controller
        return router;
      }
    }

In the comments I talked about a controller. Let's make that now. Create a file called controller.dart in the lib/ directory. Paste in the code below to handle HTTP requests:

    // dart_server/lib/controller.dart

    import 'dart:async';
    import 'dart:io';
    import 'package:aqueduct/aqueduct.dart';

    // using a list to simulate a database for demonstration purposes
    List<Map<String, dynamic>> mockDatabase = [
        {
            'fruit': 'apple',
            'color': 'red'
        },
        {
            'fruit': 'banana',
            'color': 'yellow'
        }
    ];

    class MyController extends ResourceController {

      // Handle GET (all) request
      @Operation.get()
      Future<Response> getAllFruit() async {
        // return the whole list
        return Response.ok(mockDatabase);
      }

      // Handle GET (one) request
      @Operation.get('id')
      Future<Response> getFruitByID(@Bind.path('id') int id) async {
        // error checking
        if (id < 0 || id >= mockDatabase.length){
          return Response.notFound(body: 'Item not found');
        }
        // return json for item at id
        return Response.ok(mockDatabase[id]);
      }

      // Handle POST request
      @Operation.post()
      Future<Response> addFruit() async {
        // get json from request
        final Map<String, dynamic> item = await request.body.decode(); // TODO validate
        // create item (TODO return error status code if there was a problem)
        mockDatabase.add(item);
        // report back to client
        final int id = mockDatabase.length - 1;
        return Response.ok('Item added with id $id');
      }

      // Handle PUT request
      @Operation.put('id')
      Future<Response> putContent(@Bind.path('id') int id) async {
        // error checking
        if (id < 0 || id >= mockDatabase.length){
          return Response.notFound(body: 'Item not found');
        }
        // get the updated item from the client
        final Map<String, dynamic> item = await request.body.decode(); // TODO validate
        // make the update
        mockDatabase[id] = item;
        // report back to the client
        return Response.ok('Item replaced at id $id');
      }

      // Handle PATCH request 
      // (PATCH does not have its own @Operation method so 
      // the constructor parameter is used.)
      @Operation('PATCH', 'id')
      Future<Response> patchContent(@Bind.path('id') int id) async {
        // error checking
        if (id < 0 || id >= mockDatabase.length){
          return Response.notFound(body: 'Item not found');
        }
        // get the updated item from the client
        final Map<String, dynamic> item = await request.body.decode(); // TODO validate
        // make the partial update
        mockDatabase\[id\]['color'] = item['color'];
        // report back to the client
        return Response.ok('Item updated at id $id');
      }

      // Handle DELETE request 
      @Operation.delete('id')
      Future<Response> deleteContent(@Bind.path('id') int id) async {
        // error checking
        if (id < 0 || id >= mockDatabase.length){
          return Response.notFound(body: 'Item not found');
        }
        // do the delete
        mockDatabase.removeAt(id);
        // report back to the client
        return Response.ok('Item deleted at id $id');
      }
    }

Save your changes.

Testing the server

Make sure you are inside the root of your project folder:

    cd dart_server

Normally you would start the server in the terminal like this:

    aqueduct serve

However, Aqueduct defaults to listening on port 8888. In all of the client apps and Node.js we are using port 3000, so let’s do that here, too. I'm also limiting the number of server instances (aka isolates) to one. This is only because we are using a mock database. For your production server with a real database, you can let the server choose the number of isolates to run. So start the server like this:

    aqueduct serve --port 3000 --isolates 1

The server is now running on your machine. You can use Postman (see docs and tutorial) to test the server now, or you can use one of the client apps that we will make in part three.

Supplemental code

The following is a model class that includes code to do JSON conversions. I’m including it here for your reference, but you don’t need to do anything with it today. For more help converting JSON to objects in Dart see this post.

    class Fruit {

      Fruit(this.fruit, this.color);

      // named constructor
      Fruit.fromJson(Map<String, dynamic> json)
          : fruit = json['fruit'] as String,
            color = json['color'] as String;

      int id;
      String fruit;
      String color;

      // method
      Map<String, dynamic> toJson() {
        return {
          'fruit': fruit,
          'color': color,
        };
      }
    }

Further study

Conclusion

In this tutorial, we saw two ways to create a backend server. Although the details were somewhat different, the REST API that we implemented was exactly the same. If you don’t like Node.js or Server Side Dart, there are many more to choose from. (I was playing around with Server Side Swift for a while before I decided to pursue Flutter for the frontend.) Whatever server technology you choose, just implement the REST API that we used here.

You can find the server code for this lesson on GitHub.

In the last part of this tutorial we will learn how to make Android, iOS, and Flutter client apps that are able to communicate with the servers that we made in this lesson.

Clone the project repository
  • Android
  • Dart
  • Flutter
  • iOS
  • JavaScript
  • Kotlin
  • Node.js
  • Swift
  • no pusher tech

Products

  • Channels
  • Chatkit
  • Beams

© 2019 Pusher Ltd. All rights reserved.

Pusher Limited is a company registered in England and Wales (No. 07489873) whose registered office is at 160 Old Street, London, EC1V 9BW.