Baby's first backend

This is a simple project meant to introduce you to the world of backend development, where you (yes you, with the help of the internet) make a little server to function as a to-do list.

buh what's an API really?

Well for starters, it stands for "Application Programming Interface".

Real-life apps are very complicated, with a lot of interweaving components that somehow have to talk to and cooperate with each other.

Take our hypothetical to-do app for example...we're going to need the frontend (which is how the end user interacts with the app, e.g., with a CLI or GUI), the datastore (which would store all the information, in this case, to-do items), and the backend, which is where the datastore is located and exposed for the interface to interact with it.

But we need something to be able to bridge those two (our user interface can't just go touching the database directly now, can it? It cannot). And that's where the API comes in.

The API provides a consistent communication protocol, so that various parts of the system (in this case the front- and back- ends) can interface with each other and perform any needed functions.

okay, i'm ready, gimme a tldr

  • Your goal is to implement a simple backend application that can handle a set of APIs which allow it to store and process items, just like a traditional to-do application.

  • Your API is utilising already existing HTTP request methods (because those are understood by most network-connected computers in existence and are the foundation of the modern Web)

  • You can use any language/database/connector you're comfy with, some great starters if you're unsure are:

    Databases: Any relational database like SQLite, MySQL or PostgresSQL will be a good fit for most projects.

    Language C# Go Rust Python JavaScript
    Web framework ASP.NET Core Fibre Axum Flask Express
    Database connector EF Core GORM Diesel Peewee Sequelize
  • Your API should implement these endpoints:

    Route Method Action Response Code
    /api/todos GET Returns all the to-do items already stored 200 OK
    /api/todos/{id} GET Returns a to-do item with key id 200 OK or 404 Not Found
    /api/todos POST Create a to-do item 201 Created
    /api/todos/{id} PATCH Edit the title or description of a to-do item with key id, returns the updated item 200 OK or 404 Not Found
    /api/todos/{id}/status PUT Modify the completed status of a to-do item with key id, returns the updated item 200 OK or 404 Not Found
    /api/todos/{id} DELETE Delete a to-do item with key id 200 OK or 404 Not Found

    You can find more information for the HTTP verbs and possible response codes at the MDN HTTP docs.

  • You are not required to build the frontend to test your application, there's one already provided and you can follow these instructions to link it to your own backend.

  • You can also upload your binary to a service like Render to make it available on the public web if you wish to do so.

  • An endpoint tester has also been created for this project over here, once you've made your API live you can simply go over there and make sure your implementation conforms to the spec.

sorry i'm a bit new to this whole...thing, what exactly am i doing

Let's break down what you need to should probably do into a few steps:

  1. Pick a programming language (and other tools) you're going to work with.

    If you've used one of the ones from the table above before and are comfortable in it, then just go along with it...building a backend involves breaking down the problem into various tiny steps - and those skills are applicable no matter what tools you are using and are easily transferable.

    If you're want to try something new or different, then here are some reasons for you to pick:

    • C#: It's very popular and adopted by a lot of players in the industry, and Microsoft owns it, so it's not going away anytime soon.
    • Go: It's pretty fast and has built-in support for concurrency (doing a lot of things at the same time). Plus, literally Google made it.
    • Rust: It enforces correctness in your code and helps discover any possible errors early in the development process while also offering quite good performance.
    • Python: Its syntax is very readable and easy to get used to which helps to reduce the time taken for development.
    • JavaScript: It has a great ecosystem and community with tools for basically everything you might need (or want, even).

    If you're a little confused after reading all of that, it's fine...

    All these languages are good, what matters most is the knowledge you gain from actually building the server. Pick Go if you're still torn between them and start!!!

    If you don't have any programming experience, then you'll want to get a basic refresher in whichever language you pick, some good ones are:

    • Microsoft's Introduction to C# which guides you into learning the syntax by allowing you to run interactive snippets of code right in your browser. Also check out the ASP.NET tutorial which has a high-level overview of making a web API with the tool.
    • For the love of Go, a nice intro to programming in general and the Go language. There's also an official tutorial on building a very tiny non-persistent (as in, no database) API here.
    • The Rust Book is a rally good book that covers the ins and outs of the language. At the end you even make a very, very rudimentary web server (nothing like the scale of this one though). Zero To Production in Rust also goes deeply into this subject (backends) and expands on it greatly, covering everything from testing to error handling to authentication.
    • Python Crash Course introduces the language by having you build numerous projects, exposing you to a lot of popular packages and programming fundamentals.
    • Eloquent JavaScript and Microsoft's Introduction to Node.js introduce you to the language itself and Node.js (which is the most popular way of running it outside a browser) respectively.

    Keep in mind you don't necessarily have to finish all the books from cover to cover, a lot of the information is pretty advanced and you'd probably have a better grasp of it after you've completed this project (and understand the fundamentals).

  2. Figure out how data is going to be stored in the database.

    Most likely, you'll be required to model the objects you're storing. In this case, it's a simple todo item. Using C#, you would have something roughly like this:

    class TodoItem 
    {
        int ID;
        string title;
        string description;
        bool completed;
        DateTime createdAt;
        DateTime lastUpdatedAt;
    }

    Then you'll have to connect this model to your database and instruct it to set itself up so you can put it in. The instructions are going to differ, depending on which connector you use, so the documentation is going to be the best place to start.

  3. Set up your app to perform actions based on requests sent to it.

    Using your web framework, you can monitor HTTP requests to specific URLs (using routes) and take certain actions based on which ones are sent (using handlers). You then have to assign these requests to interact with the database in some way, using the models you've made, like looking for the existence of a to-do item and returning it, or an error if it isn't found.

    Using Axum, can look (somewhat) like this:

    async fn main() {
        let app = Router::new()
            // `GET /api/todos` goes to `getTodos`
            .route("/api/todos", get(getTodos))
            // `POST /api/todos` goes to `makeTodo`
            .route("/api/todos", post(makeTodo));
    }
    
    // Handlers
    async fn getTodos() -> StatusCode {
        // checks for the existence of a to-do item in the database
        // returns a status code like 200 OK
    }
    
    async fn makeTodo(Json(payload): Json<TodoItem>) -> StatusCode {
        // takes encoded information passed in the request body
        // make a new to-do item
        // then return a status code
    }
  4. Test your endpoints.

    Manually making sure everything works as intended is a Herculean task, which is why integration tests are key in a project like this. Ideally you should:

    • Pick a nice testing framework, the one that comes inbuilt with your language might be okay but look around to see if there's any more suited to API endpoint testing (or just looks nicer) such as xUnit, gotestsum, Nextest, pytest, and Jest.

    • Make your (integration) tests resemble the real-life application as much as possible, in this case, a browser. Unit tests are fine and all, but integration tests treat your application as a black box. You'll probably want a simple HTTP client package/library in your language of choice:

      • ASP.NET's testing package handles spinning up a TestServer for you.
      • Go has a great one that comes included with the standard library.
      • reqwest is one of the more popular Rust options.
      • Requests is one of the most downloaded Python packages, that alone should speak for itself.
      • Supertest is a nice option for JavaScript users.
    • Write atomic tests that only check a little bit of functionality separately because rolling a bunch of tests into one can cause headaches when just one fails and messes everything else up. Imagine this:

      tests:
          - ensure GET /api/todo/{id} returns 404 with invalid id...failed BAD
          - ensure GET /api/todo/{id} returns 200 with valid id...passed OK
          - ensure POST /api/todo fails with empty description...passed OK
      
      passed 2/3 - 67%, failed 1/3 - 33%, skipped 0/3 - 0%
      

      As opposed to:

      tests:
          - test all the things...failed BAD
      
      passed 0/1 - 0%, failed 1/1 - 100%, skipped 0/3 - 0%
      

      This is an extreme example...but you can see the first one provides so much more info on what works and what doesn't. Break down your tests as much as possible, it'll serve you better in the long run.

    • Think (hard) about what your endpoint should achieve, and write appropriate tests before you even start implementing the handler. That way, you don't get boxed into testing just the cases you've accounted for after writing the function.

    • Try, as much as reasonably possible, to make tests isolated and idempotent (they should be able to run repeatedly without affecting the results of other tests). For a project like this, where a database is being modified, features like transactions (docs for MySQL, PostgresSQL, SQLite) can allow you to make a change to the database, test a certain aspect, and then revert the change so that other tests aren't affected by lingering "effects" and can therefore be more accurate.

    You can also consider the use of a container using engines like Docker or Podman to spin up for a test, put in any needed data, and then get disposed of afterwards.

  5. Couple the frontend application with your backend.

    If you'd like to see a functioning version of what the final application could look like, follow the instructions in the Adding Frontend document to get started with that.

  6. Put it up for the world to see.

    If you want you can just put the API up using a service like Render, and try out our endpoint tester on it. You could also make a frontend application that builds on it if you're so inclined, and host that using a service like Vercel.

Next steps

We hope you had fun for this ride. It was pretty unsupervised but that's the joy of it -- doing this by yourself will be hard at the beginning, but it gets you used to looking up information and finding ways to solve problems, as opposed to just being spoon-fed information.

If you're interested doing more in backend development, the linked resources above really good -- try going through them if you didn't finish the first time around. The Internet is also a great place to check out, and there's tons of resources out there to get you even further along in the field.

If you've completed this project, do add it to the showcase repo, we'd love to see what tools you used and how you chose to overcome these problems.