Quick Start Guide Todo List

What you'll build

In this guide, you will quickly get Alpas up-and-running on your local machine and create a simple to-do app.

Don't worry about your skill level - this quick start guide is meant to provide step-by-step instructions useful to those of any skill level. πŸ’ͺ🏽

What you'll learn

/tip/ You'll learn quite a bit building this simple app.

  • How to get Alpas setup and running on your local device
  • How to connect with a MySQL database
  • How to create database tables in Alpas and migrate them
  • How to create, retrieve, update, and delete data in your MySQL database
  • How to create a database entity object
  • How to create a controller
  • How to add routes
  • How to interact with the MySQL database using Ozone
  • How to protect your app against the cross site request forgery attacks
  • How to connect an interactive front-end with the powerful Alpas back-end
  • How to create a fun, yet useful to-do list! βœ…

And, this only scratches the surface of Alpas's features!

What you'll need

Before moving to the first step, go through the following list and make sure you have everything you will need to get started. Don't worry, everything you need is free to use!

Let's Get Started!

/info/ If you have previously successfully used the starter template and served an Alpas app, jump ahead to Step 3: Setup your database

Step 1: Setup your environment

Setting up your environment is quick and painless.

  1. Refer to the setup instructions and perform the necessary steps. Make sure you have Java version 9.0 or higher installed.
  2. If using Windows, be sure to watch Setting up GitBash and SDKMan on Windows and Alpas Installation on Windows With WSL by AlpasCasts.

Step 2: Create and add the starter project to your local machine

Now that your environment is all setup, let's get rollin' on creating your very first Alpas app! πŸŽ‰

The following steps are taken from the Installation section, but we will fill in some additional details for your to-do app.

Create to-do project on GitHub and clone to your local machine

  1. Go to the Alpas Starter project on GitHub
  2. Click the green Use this template button
  3. On the 'Create a new repository from starter' page, type todolist for 'Repository name'
  4. Click Create repository from template - you now copied the startup template to your GitHub account!
  5. On your new todolist repo page, select Clone or download
  6. You will have an option to use HTTPS or SSH - let's use HTTPS for now
  7. Pull the repo down to your local

Open to-do project on your local machine and make some top level configs

  1. Open IntelliJ and go to File > Open and find the root folder, todolist. Double-click it to open.
  2. If you are prompted to import Gradle, go ahead and do so.
  3. In IntelliJ, locate the Terminal tab.
  4. In the terminal, type in chmod +x ./alpas and hit return - this will make Alpas executable.
  5. Next, in terminal, type ./alpas init com.todo.list and hit return - this will initialize your app.
  6. While we are in terminal, type ./alpas help - this displays a list of commands you can run to find information about your app as well as provide quick actions such as creating new entities.
  7. Before we run the project, let's check to make sure IntelliJ is set to build using Java 9.0 - Go to File > Project Structure, then go to Project and make sure Project SDK has Java 9 selected; if it doesn't, follow instructions on how to serve Alpas with IntelliJ in the Installation documentation.
  8. Now, find start.kt in your project - you can either tap the shift key twice to open quick search or follow the path todolist > src > kotlin > start.kt.
  9. Right click on start.kt and select option to Run
  10. Once run is successful, click http://localhost:8080/ to view the project in your browser

TADA! πŸŽ‰

You are viewing the basic Alpas start screen. But, there is still a bit more to do to create your to-do list. Now that we have Alpas successfully installed and running on your machine, let's create the actual app!

Step 3: Setup your database

Let's first start by creating a new database on your local machine. If you haven't already installed MySQL Server and have it running on your machine, refer to the What you'll need section for links to set up MySQL.

Create the to-do database in Sequel Pro

  1. Open Sequel Pro and add a new connection.
  2. Type 127.0.0.1 into Host an root for the Username.
  3. If you entered a password when installing MySQL, add in the password; otherwise, leave it blank.
  4. Click Connect.
  5. Once connected to your local MySQL server, go to Database > Add Database and create a new database named todolist

That's all for now in Sequel Pro. We will add tables and fields during the next steps.

Connect your to-do project to MySQL database

  1. Go back to the to-do project up in IntelliJ and navigate to the .env file under the project root folder.
  2. In .env, update DB_PASSWORD=secret to replace 'secret' with your password; leave blank if you did not setup your MySQL database with a password.
  3. Update DB_DATABASE to point to todolist.
  4. Go to the DatabaseConfig.kt file in todolist > src > main > kotlin > configs.
  5. Uncomment // addConnections(env) by removing //.
  6. Run the project .

If you run and are able to successfully access http://localhost:8080/, then congrats! You have successfully connected the project to your MySQL database.

Step 4: Setup your task entity

  1. In terminal, type ./alpas make:entity task and tap return
  • this will create a new entity 'Task' and corresponding table under src > main > kotlin > entities folder.
  1. Update task.kt to look like the following - check out the helper comments in the code.

package com.todo.list.entities

import dev.alpas.ozone.*
import me.liuwj.ktorm.schema.boolean
import java.time.Instant

interface Task : OzoneEntity<Task> {
    var id: Long
    var name: String?
    var createdAt: Instant?
    var updatedAt: Instant?
    
    // Add a boolean property for completed todos
    val completed: Boolean

    companion object : OzoneEntity.Of<Task>()
}

object Tasks : OzoneTable<Task>("tasks") {
    val id by bigIncrements()
    val name by string("name").size(150).nullable().bindTo { it.name }
    val createdAt by createdAt()
    val updatedAt by updatedAt()

    // Add a completed column for completed todos
    val completed by boolean("completed").default(false).bindTo { it.completed }
}

Step 5: Setup your task controller

  1. In IntelliJ's terminal, type ./alpas make:controller TaskController and tap return - this creates a 'TaskController' under todolist > src > main > kotlin > controllers folder
  2. Update TaskController.kt to look like the following - check out the helper comments in the code

package com.todo.list.controllers

import dev.alpas.http.HttpCall
import dev.alpas.routing.Controller

// add the following imports
import com.todo.list.entities.Tasks // This calls the Tasks entity created in step 4
import dev.alpas.orAbort
import me.liuwj.ktorm.dsl.delete
import me.liuwj.ktorm.dsl.update
import dev.alpas.ozone.create
import dev.alpas.ozone.latest
import dev.alpas.validation.min
import dev.alpas.validation.required
import me.liuwj.ktorm.dsl.*
import me.liuwj.ktorm.entity.toList

class TaskController : Controller() {
    fun index(call: HttpCall) {

        // Go ahead and remove the following line
        // call.reply("Hello, TaskController!")

        // ADD IN THE FOLLOWING
        
        // Get the latest todo items from the database
        val tasks = Tasks.latest().toList()  
        // Get the total size of the tasks
        val total = tasks.size 
        // Now, get the completed todo items 
        val completed = tasks.count { it.completed } 
        
        // Now render the 'welcome' page to display a list of todo items to the user
        call.render ("welcome", mapOf("tasks" to tasks, "total" to total, "completed" to completed))
    }
    
    // Let's create a function to store a new todo item that have been added via a form.
    fun store(call: HttpCall) {
        
        // Before we write the new todo task to the database, let's first validate to
        // make sure there is data with at least 2 characters. If validationfails,
        // a message will be sent back to the user with the failure reasons.
        call.applyRules("newTask") {
            required()
            min(2)
        }.validate() 
        
        // If validation has passed, then create a new todo item in the databse
        Tasks.create() {
            // Get the name of the task that was passed
            val taskName = call.stringParam("newTask")
            it.name to taskname
        }
        
        // If a new todo task has successfully been created and saved to the database,
        // let's send a success message back to the user to let them know.
        flash("success", "Successfully added to-do")
        call.redirect().back()
    }
    
    // Next up, let's create a function to delete a todo item
    fun delete(call: HttpCall) {
        // We will get the todo tasks id that is marked for deletion
        // and then remove that item from database
        val id = call.longParam("id").orAbort() 
        Tasks.delete { it.id eq id }
        flash("success", "Successfully removed to-do")

        // Go back to the same page that the user came from
        call.redirect().back()
    }
    
    // Lastly, let's create a function to update a todo task
    // to make as either completed or uncompleted.
    fun update(call: HttpCall) {
        // Get the intended boolean state of todo task as determined by a checkbox.
        // If the checkbox was not checked this will return a null. 
        
        val markAsComplete = call.param("state") != null 

        // The id of the todo item we are trying to update.
        val id = call.longParam("id").orAbort()
        
        // Update the item in the database to set the new completion state.
        Tasks.update { 
            it.completed to markAsComplete
            where { it.id eq id }
        }
        
        // Based on if markAsComplete is equal to 'True',
        // let's flash the appropriate message.
        if (markAsComplete) {
            flash("success", "Successfully completed the to-do")
        } else {
            flash("success", "Successfully updated the to-do")
        }
        
        call.redirect().back()
    }
}

Step 6: Setup your routes

  1. Navigate to the new routes.kt at todolist > src > main > kotlin
  2. Update the page to look like the following - check out the helper comments in the code

package com.todo.list

// Update WelcomeController to TaskController
import com.todo.list.controllers.TaskController 
import dev.alpas.routing.RouteGroup
import dev.alpas.routing.Router

// https://alpas.dev/docs/routing
fun Router.addRoutes() = apply {
    group {
        webRoutesGroup()
    }.middlewareGroup("web")

    apiRoutes()
}

private fun RouteGroup.webRoutesGroup() {
    // Update WelcomeController to TaskController
    get("/", TaskController::index).name("welcome") 

    // Add the following routes
    post("/", TaskController::store).name("store")
    delete("/", TaskController::delete).name("delete")
    patch("/", TaskController::update).name("update")
}

private fun Router.apiRoutes() {
    // register API routes here
}

Step 6: Migrate tables to database

We are super close! Just a few more steps! In this step we will create the to-do table and migrate to the todolist database.

  1. In terminal, type ./alpas make:migration create_tasks_table --create=tasks - this will create new data migration script
  2. In terminal, type ./alpas db:migrate this will migrate the tasks table and columns we created earlier to your checklist db - go ahead and check it out in Sequel Pro

Step 7: Update the front end

This last major step is all about updating the front end.

In IntelliJ, open the welcome.peb file at todolist > src > main > resources > templates and update to look like the following.


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <title>Alpas - To-Do List</title>
  <link rel="stylesheet" href="{{ mix('css/app.css') }}">
  <link href="https://fonts.googleapis.com/css?family=Open+Sans&display=swap" rel="stylesheet">
</head>

<body class="bg-orange-100 h-screen">

<!-- This section displays the flash messages received from TaskController -->
<div class="container-fluid fixed w-full flex justify-center">
  {% if hasFlash('success') %}
    <div class="w-1/3 text-white bg-green-500 py-2 px-10 text-center">
      {{ flash('success') }}
    </div>
  {% endif %}
</div>

<div class="container mx-auto pt-20 px-20">
  <div class="w-full flex justify-between mx-auto">
    <div class="w-1/2 text-4xl font-extrabold text-gray-700"> To-Do List</div>
    <div class="text-center">
      <!-- Here we show completed tasks vs total tasks -->
      <h5 class="text-4xl font-extrabold text-blue-600"> {{ completed }} / {{ total }}</h5>
      <p class="text-sm text-gray-600">completed / total</p>
    </div>
  </div>

  <div>
    <!-- This form is where the user enters the to-do item and makes a post request -->
    <form method="POST" action="{{ route('store') }}">
      <div class="form-group">
        <!-- CSRF is a protection mechanism; 
            view https://alpas.dev/docs/csrf-protection#main for more info -->
        {% csrf %}
        <input type="text" class="form-control mt-2 w-1/3 py-3 px-2 bg-gray-200 rounded-sm"
            id="task" name="newTask" autofocus="autofocus" placeholder="Go fishin'">
        <button type="submit" class="ml-2 p-3 px-8 inline-block bg-green-500 text-green-100">
            Add
        </button>
      </div>
      <!-- This checks to make sure to-do entry is a valid entry -->
      {% if hasError("newTask") %}
        <div class="text-red-600 mt-3">{{ firstError("newTask") }}</div>
      {% endif %}
    </form>
  </div>

  <div class="py-10">
    <!-- If no to-dos, display the following message -->
    {% if total == 0 %}
      <div class="alert alert-warning"> Add some to-dos! </div>
    {% else %}
      <!-- If to-dos do exist, then we iterate on displaying each to-do item -->
      <div>
        <ul class="text-xl">
          {% for task in tasks %}
            <li>
              <div class="flex pt-1">
                <div class="pr-4">
                  <!-- This form let's the user mark a todo task as completed or not 
                        and makes patch call to update database -->
                  <form action="{{ route('update', {"id": task.id}) }}" method="POST">
                    {% csrf %}
                    <input type="hidden" name="_method" value="patch"/>
                    <input type="checkbox" name="state"
                           onChange="this.form.submit()" {{ task.completed ? 'checked' : '' }} />
                  </form>
                </div>

                {% if task.completed %}
                  <div class="line-through"> {{ task.name }} </div>
                {% else %}
                  <div class=""> {{ task.name }} </div>
                {% endif %}
                <div class="pl-3">
                  <!-- This form lets user delete a to-do task -->
                  <form action="{{ route('delete', {"id": task.id}) }}" method="POST">
                    {% csrf %}
                    <input type="hidden" name="_method" value="delete"/>
                    <button class="hover:text-red-800 text-red-600 text-sm hover-target">
                    remove
                   </button>
                  </form>
                </div>
              </div>
            </li>
          {% endfor %}
        </ul>
      </div>
    {% endif %}
  </div>
</div>

</body>

</html>

Step 8: Run the to-do app and play around

  1. In IntelliJ, click Run to run app
  2. Once successfully running, click on the localhost to view the to-do list

HURRAY!!! You created your very first Alpas app!!! πŸŽ‰πŸŽ‰πŸŽ‰

Now, play around with your new to-do list. πŸ•Ή