DEV Community

John Rush
John Rush

Posted on • Updated on

I built a todo app using 9 different languages

Hi, I'm John, Multi Startup Builder.
I enjoy both coding and marketing.
See all my 20 products here
โ†’ johnrush.me
โ†’ my BIO
โ†’ Say Hi to me On Twitter

โ†’ Try my website builder

Greetings, fellow web developers!
Today we're going on an epic journey through not one, not two... but nine different web frameworks! ๐Ÿคฏ

mindblown

But first... Why did I do this? Well as a founder and CEO of MarsX - lowcode platform (since age 6 ๐Ÿคฏ), who lived in many countries and has lofty dreams to transform the software development world; it was just another day at work!

That's right - I painstakingly built a simple todo list app in each of these languages and documented my experience for your reading pleasure.

Without further ado, let's meet our contenders:

  1. Next.js
  2. Ruby on Rails
  3. Python Django
  4. PHP Laravel
  5. Java Spring
  6. C# .NET
  7. Go Gin
  8. Swift Vapor
  9. Kotlin Ktor
  10. MarsX (bonus)

For each language/framework combo, we'll discuss setup timeโฑ๏ธ ,routing๐Ÿšฆ ,database integration๐Ÿ’พ ,as well as overall architecture. And ofc, you'll see the code...my spagetti code :D

1. Next.js

import { useState } from 'react'

export default function TodoList() {
  const [todos, setTodos] = useState([])
  const [inputValue, setInputValue] = useState('')

  const addTodo = () => {
    if (inputValue) {
      setTodos([...todos, inputValue])
      setInputValue('')
    }
  }

  return (
    <div>
      <h1>Todo List</h1>

      <ul>
        {todos.map((todo) => (
          <li key={todo}>{todo}</li>
        ))}
      </ul>

      <form onSubmit={(e) => e.preventDefault()}>
        <input
          type="text"
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
        />

        <button onClick={() => addTodo()}>Add</button>
      </form>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Next.js brings React to life with its easy setup process and great developer experience out-of-the-box!

Setup: Easy peasy lemon squeezy ๐Ÿ‹
Routing: Built-in dynamic routing.
Database integration: Use any database you like, as long as it's supported by Node.js.

2. Ruby on Rails

# todo.rb

class Todo < ApplicationRecord
  validates :title, presence: true
end

# todos_controller.rb

class TodosController < ApplicationController
  before_action :set_todo, only: [:show, :edit, :update, :destroy]

  def index
    @todos = Todo.all.order(created_at: :desc)
  end

  def create
    @todo = Todo.new(todo_params)

    if @todo.save
      redirect_to todos_path, notice: 'Todo was successfully created.'
    else 
      render 'new'
    end 
  end 

private 

def set_todo 
   @todo = Todo.find(params[:id])
end 

def todo_params 
   params.require(:todo).permit(:title) # add additional parameters as needed.  
end 

end

# index.html.erb

<h1>Todos</h1>

<ul>
<% @todos.each do |t| %>
<li><%= t.title %></li>
<% end %>
</ul>

<%= form_for(@todo) do |f| %>  
 <%= f.text_field :title %>   
 <%= f.submit "Add" %>              
<% end %>
Enter fullscreen mode Exit fullscreen mode

Ah, the classic Ruby on Rails. This framework has been around for ages and remains one of my favorites!

Setup: rails new and off we go! ๐Ÿš€
Database integration: ActiveRecord has your back, making it easy to work with databases.
Routing: RESTful routing is built-in and simple to use.

3. Python Django

# models.py
from django.db import models

class TodoItem(models.Model):
    title = models.CharField(max_length=200)
    completed = models.BooleanField(default=False)

    def __str__(self):
        return self.title


# views.py
from django.shortcuts import render
from .models import TodoItem

def todo_list(request):
    todo_items = TodoItem.objects.all()
    context = {
        'todo_items': todo_items,
    }
    return render(request, 'todo/todo_list.html', context)


# urls.py (inside app folder)
from django.urls import path
from .views import todo_list

app_name = 'todo'
urlpatterns = [
  path('', todo_list, name='list'),
]


# templates/todo/todo_list.html
{% extends "base.html" %}

{% block content %}
  <h1>Todo List</h1>
  <ul>
  {% for item in todo_items %}
      <li>{{ item.title }} - {{ item.completed }}</li>
  {% endfor %}
  </ul>
{% endblock %}
Enter fullscreen mode Exit fullscreen mode

Django - the web framework "for perfectionists with deadlines" - doesn't disappoint. Plus, who doesn't love Python?

Setup: django-admin startproject gets you up and running quickly.
Routing: URL configuration can be a bit more complex than other frameworks but is still manageable.
Database integration: ORM makes database interactions smooth sailingโ›ต๏ธ .

4. PHP Laravel

// routes/web.php
Route::get('/', 'TodoController@index');
Route::post('/todos', 'TodoController@store');

// Todo model
class Todo extends Model {
  protected $fillable = ['title'];
}

// Todo controller
class TodoController extends Controller {
  public function index() {
    $todos = Todo::all();
    return view('todo.index', compact('todos'));
  }

  public function store(Request $request) {
    $this->validate($request, [
      'title' => 'required'
    ]);

    Todo::create(['title' => request('title')]);

    return redirect('/');
  }
}

// resources/views/todo/index.blade.php
<form method="POST" action="/todos">
    {{ csrf_field() }}

    <label>Title:</label>
    <input type="text" name="title">

    <button type="submit">Add</button>
</form>

<ul>
@foreach ($todos as $todo)
<li>{{ $todo->title }}</li>
@endforeach
</ul>
Enter fullscreen mode Exit fullscreen mode

Laravel brings elegance to PHP development by providing clean syntax and an enjoyable developer experience.

Setup: A breeze with Composer (composer create-project --prefer-dist laravel/laravel my_todo_app)
Routing: Simple web.php file for defining routes ๐Ÿ—บ๏ธ .
Database integration: Eloquent ORM keeps everything neat and tidy.

5. Java Spring

@RestController
@RequestMapping("/todos")
public class TodoController {

    private final TodoRepository todoRepository;

    public TodoController(TodoRepository todoRepository) {
        this.todoRepository = todoRepository;
    }

    @GetMapping("/")
    public List<Todo> getAllTodos() {
        return this.todoRepository.findAll();
    }

    @PostMapping("/")
    public ResponseEntity<Object> createTodo(@RequestBody Todo newTodo) {
        try {
            this.todoRepository.save(newTodo);
            return ResponseEntity.ok().build();
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
     }
}

@Entity
@Table(name = "todos")
public class Todo {

  @Id
  @GeneratedValue(strategy=GenerationType.AUTO)
  private Long id;

  private String title;

  private boolean completed;

}
Enter fullscreen mode Exit fullscreen mode

Java Spring brings the power of Java to web development with a robust framework and plenty of configuration options.

Setup: A bit more involved, but manageable.
Routing: Annotated controllers make routing a breeze.
Database integration: JPA provides solid database support.

6. C# .NET

using System;
using System.Collections.Generic;

class Program {
    static void Main(string[] args) {
        List<string> todos = new List<string>();

        Console.WriteLine("Todo list");

        while (true) {
            Console.Write("> ");
            string input = Console.ReadLine();

            if (input == "quit") {
                break;
            }

            if (input.StartsWith("+")) {
                string todo = input.Substring(1).Trim();
                todos.Add(todo);
                Console.WriteLine($"Added: {todo}");
            } else if (input.StartsWith("-")) {
                int indexToRemove = int.Parse(input.Substring(1).Trim()) - 1;

                if (indexToRemove >= 0 && indexToRemove < todos.Count) {                    
                    string removedTodo = todos[indexToRemove];
                    todos.RemoveAt(indexToRemove);
                    Console.WriteLine($"Removed: {removedTodo}");
                }
            } else if (input == "") {
                Console.WriteLine("Todos:");

                 for(int i=0; i<todos.Count; i++){
                     Console.WriteLine($"{i+1}: {todos[i]}");
                 }
             }else{
                 //invalid command entered
             }
         }
     }
}
Enter fullscreen mode Exit fullscreen mode

C# .NET offers top-notch performance and comes packed with features for building full-stack web apps.

Setup: Use Visual Studio or CLI tools - either way, it's smooth sailingโ›ต๏ธ .
Routing: Attribute-based routing lets you define routes directly on your controller methods ๐ŸŽฏ .
Database integration: Entity Framework Core is powerful and well-integrated into the ecosystem.

7. Go Gin

package main

import (
    "github.com/gin-gonic/gin"
)

type Todo struct {
    ID        int    `json:"id"`
    Title     string `json:"title"`
    Completed bool   `json:"completed"`
}

var todoList []Todo

func main() {
    r := gin.Default()

    // GET all todos
    r.GET("/todos", func(c *gin.Context) {
        c.JSON(200, todoList)
    })

    // POST a new todo
    r.POST("/todos", func(c *gin.Context) {
        var newTodo Todo
        c.BindJSON(&newTodo)

        // Generate unique ID for the new todo item
        if len(todoList) == 0 {
            newTodo.ID = 1 
        } else { 
            lastID := todoList[len(todoList)-1].ID 
            newTodo.ID = lastID + 1 
        }

        // Append the newly created item to the list of todos.
        todoList = append(todoList, newTodo)

        c.JSON(201, gin.H{
            "message": "New ToDo added successfully",
            "todo": &newToDo,
         })
    })

    r.Run(":8080")
}
Enter fullscreen mode Exit fullscreen mode

Gin is an excellent choice for those who prefer the simplicity and speed of Go. It's lightweight yet fully featured!

Setup: Easy setup using go mod init๐Ÿš€ .
Routing: Simple route definition in just a few lines of code!
Database integration: GORM makes working with databases enjoyable ๐Ÿ’พ.

8. Swift Vapor

import Vapor

final class Todo: Content {
    var id: Int?
    var title: String

    init(title: String) {
        self.title = title
    }
}

func routes(_ app: Application) throws {

    // create a new todo item
    app.post("todo") { req -> EventLoopFuture<Todo> in
        let todo = try req.content.decode(Todo.self)
        return todo.save(on: req.db).map { todo }
    }

    // get all todos from the database
    app.get("todos") { req -> EventLoopFuture<[Todo]> in
        return Todo.query(on: req.db).all()
    }

}
Enter fullscreen mode Exit fullscreen mode

Vapor brings the power of Swift to server-side development, and it's almost as delightful as a freshly-baked ๐Ÿ pie!

Setup: A bit more involved due to Xcode setup time.
Routing: Route definition is straightforward and readable.
Database integration: Fluent ORM is powerful and expressive.

9. Kotlin Ktor

data class Todo(val id: Int, val task: String)

val todos = mutableListOf<Todo>()

fun main() {
    embeddedServer(Netty, port = 8080) {
        install(ContentNegotiation) {
            json()
        }

        routing {
            get("/todos") {
                call.respond(todos)
            }

            post("/todos") {
                val todo = call.receive<Todo>()
                todos.add(todo)
                call.respond(HttpStatusCode.Created)
            }

            delete("/todos/{id}") {
                val id = call.parameters["id"]?.toIntOrNull()

                if (id == null || !todos.removeIf { it.id == id }) 
                    call.respond(HttpStatusCode.NotFound)
                 else 
                    call.respond(HttpStatusCode.OK)    
           }
       }  
   }.start(wait = true)
}
Enter fullscreen mode Exit fullscreen mode

Ktor helps you build web apps with Kotlin, combining simplicity with the power of JetBrains' language innovations๐Ÿ’ก .

Setup: Requires some initial configuration but manageable.
Routing: Uses DSL for defining routes - clean and elegant๐ŸŽฉ .
Database integration: Exposed library makes working with databases simple yet powerfulโš™๏ธ .

10. MarsX (Bonus) ๐Ÿš€

Hold on to your hats, folks! We've got a brand new framework/language that's so ridiculously easy, even my grandma managed to build the todo list in just 7 minutes!

But here's the catch - it's still in private beta, and you'll have to battle through a waitlist like Black Friday shoppers trying to snag that sweet deal. Want in? Join the frenzy here.

Funny gif of people fighting for deals

<schema>
  <array name="todo">
    <object>
      <string name="title" />
    </object>
  </array>
</schema>
Enter fullscreen mode Exit fullscreen mode

And there you have it!

Nine different frameworks used to build the same app - may your startup grow into a deca unicorn๐Ÿฆ„ , no matter which one you choose. Just remember to choose wisely; after all, your framework will dictate your life for the next nine years๐Ÿ˜‰ .

choosewisely

Happy coding!

--

Hi, I'm John, Multi Startup Builder.
I enjoy both coding and marketing.
See all my 20 products here
โ†’ johnrush.me
โ†’ my BIO
โ†’ Say Hi to me On Twitter
โ†’ Try my website builder

Top comments (14)

Collapse
 
efpage profile image
Eckehard • Edited

You could also add VanJS to the party, which is pretty cool. The ToDo-app in VanJS needs only 17 lines of code:

const TodoItem = ({text}) => {
  const done = van.state(false), deleted = van.state(false)
  return van.bind(deleted,
    d => d ? null : div(
      input({type: "checkbox", checked: done, onclick: e => done.val = e.target.checked}),
      van.bind(done, done => done ? strike(text) : span(text)),
      a({onclick: () => deleted.val = true}, "โŒ"),
    )
  )
}

const TodoList = () => {
  const inputDom = input({type: "text"})
  const dom = div(inputDom,
    button({onclick: () => van.add(dom, TodoItem({text: inputDom.value}))}, "Add"),
  )
  return dom
}
Enter fullscreen mode Exit fullscreen mode

You can try it on jsfiddle

Collapse
 
johnrushx profile image
John Rush

I will make a new article with all the recommendations from the comments.
VanJS looks cool

Collapse
 
egomesbrandao profile image
Emmanuel G. Brandรฃo

Why not share the repositories on Github?

Collapse
 
johnrushx profile image
John Rush

good point, will do it for next articles, I haven't really used github for small code snippets, I make them mostly in replit, marsx or other online ides

Collapse
 
egomesbrandao profile image
Emmanuel G. Brandรฃo

I'm a DevOps guy, and I'm interested in build a simple app to use in my demos of build a pipeline or a cloud infrastructure... this examples, maybe, could be one!

Collapse
 
fen1499 profile image
Fen

Really cool idea, I've been thinking about something similar for quite some time but never really got to do it

Collapse
 
johnrushx profile image
John Rush

same languages? or you think there are more I should have covered?

Collapse
 
fen1499 profile image
Fen

Not as many languages, I was thinking about four or five. One that I thought about and is not in the list is elixir

Thread Thread
 
johnrushx profile image
John Rush

hah, funny part, I had it, but It felt like too many, so I decided to not include it :D

defmodule Todo do
  defstruct [:id, :title, :completed]

  def create(attrs \\ %{}) do
    %Todo{
      id: attrs[:id] || generate_id(),
      title: attrs[:title],
      completed: false
    }
  end

  defp generate_id(), do: :crypto.strong_rand_bytes(8)

end

todos = [
   Todo.create(title: "Task1"),
   Todo.create(title: "Task2"),
   Todo.create(title: "Task3")
]

Enum.each(todos, fn todo ->
  IO.puts "#{todo.id} - #{todo.title}"
end)
Enter fullscreen mode Exit fullscreen mode
Collapse
 
bretbernhoft profile image
Bret Bernhoft

Would you recommend Next.js over React.js as a building tool? I've been hearing good things about Next.js and would love to give it a spin soon. Thank you for the information and programming demos.

Collapse
 
johnrushx profile image
John Rush

nextjs looks really good, im building devhunt.org on it

Collapse
 
johnrushx profile image
John Rush

credits to fireship for all my inspirations

Collapse
 
alexanderisora profile image
Alexander Isora ๐Ÿฆ„

so in marsx you will just import the todolist microapp and use it?

Collapse
 
johnrushx profile image
John Rush

in the example here, I've actually built it from scratch, and itwas like 9 lines.
If you import, then it's 1 line only.