Build a React application with a no-code backend (DoTenX) in 5 minutes

Sam - Jun 30 '22 - - Dev Community

Today, being a front-end developer is enough to be able to build an entire application. You might ask how. It's simple, you can use DoTenX, an open-source no-code backend builder turning you into a super-developer!

In DoTenX you can easily do user management (sign in/ sign up), create database tables, workflows and even complex background workflows which are triggered based on different events. The interesting part is that you can do all of that without writing a single line of code!

Also, another interesting fact about DoTenX for all the open-source fans, is that DoTenX is open-source: https://github.com/dotenx/dotenx (We appreciate your support by adding stars to our project).

DoTenX is in pre-alpha version at the moment and at dotenx.com you can create a free account and try whatever I demonstrate in this tutorial along with me on your account.

In this post, we create a simple To-do list application that has three pages:

  • Sign up
  • Login
  • Todos

The Todos page is a protected page, meaning you have to be already logged in to be able to visit the page.

In the next tutorials we'll continue this example to build more advanced applications.


Preparation

The first step is to create an account here.

After you signed up, on your admin dashboard, click on App Builder.

Now, we start by creating a project:

After creating the project, simply click on the project you just created to go to the Builder Studio.

Now we're ready to start building our application!

Let's create our React application with Vite:

 npm create vite@latest
Enter fullscreen mode Exit fullscreen mode

Following this command, we'll be asked about the name of the project and the framework. Make sure you use React as the framework.

You can also optionally add tailwindcss to your project. You can find the instructions here. As a side note, you should skip the first step (creating the React application), as we already did it.

Routing

Let's st

npm i react-router-dom
Enter fullscreen mode Exit fullscreen mode

Now we change App.jsx to look like this:

App.jsx

import "./App.css";
import { BrowserRouter, Route, Routes, Navigate } from "react-router-dom";

function App() {
  return (
    // Add the Router to the App component. It should have a Switch component inside it to handle the routes.
    <BrowserRouter>
      <Routes>
        <Route path="/signup" element={<SignUp />}></Route>
        <Route path="/login" element={<Login />}></Route>
        <Route path="/todos" element={<Todos />}></Route>
      </Routes>
    </BrowserRouter>
  );
}

function SignUp() {
  return (
    <div>SignUp</div>
  );
}

function Login() {
  return (
    <div>Login</div>
  );
}

function Todos() {
  return (
    <div>Todos</div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

We'll then start creating each of the pages and finally we'll update the routes to make sure the users have to be logged in to be able to visit the /todos route.

Sign up

Let's add a simple form for our sing up page:

SignUp.jsx

import { useState } from "react";
import { useNavigate } from "react-router-dom";
import authService from "./auth.service";

function SignUp() {
  const navigate = useNavigate();

  // Add the SignUp form
  const [form, setForm] = useState({
    email: "",
    password: "",
  });
  const handleChange = (e) => {
    setForm({ ...form, [e.target.name]: e.target.value });
  };
  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      await authService().register(form.fullname, form.email, form.password);
      alert("Successfully signed up");
      navigate("/login");
    } catch (error) {
      alert(error.message);
    }
  };
  return (
    <div class="flex items-center justify-center min-h-screen bg-gray-100">
      <div class="px-8 py-6 mt-4 text-left bg-white shadow-lg">
        <h3 class="text-2xl font-bold text-center">Create an account</h3>
        <form onSubmit={handleSubmit}>
          <div class="mt-4">
            <div>
              <label class="block" for="fullname">
                Name
              </label>
              <input
                type="text"
                placeholder="Name"
                name="fullname"
                onChange={handleChange}
                className="w-full px-4 py-2 mt-2 border rounded-md focus:outline-none focus:ring-1 focus:ring-blue-600"
              ></input>
            </div>
          </div>
          <div class="mt-4">
            <div>
              <label class="block" for="email">
                Email
              </label>
              <input
                type="text"
                placeholder="Email"
                name="email"
                onChange={handleChange}
                className="w-full px-4 py-2 mt-2 border rounded-md focus:outline-none focus:ring-1 focus:ring-blue-600"
              ></input>
            </div>
            <div class="mt-4">
              <label class="block">Password</label>
              <input
                type="password"
                placeholder="Password"
                name="password"
                onChange={handleChange}
                className="w-full px-4 py-2 mt-2 border rounded-md focus:outline-none focus:ring-1 focus:ring-blue-600"
              ></input>
            </div>
            <div class="flex items-baseline justify-between">
              <button class="px-6 py-2 mt-4 text-white bg-blue-600 rounded-lg hover:bg-blue-900">
                Sign up
              </button>
            </div>
          </div>
        </form>
      </div>
    </div>
  );
}

export default SignUp;
Enter fullscreen mode Exit fullscreen mode

Nothing special about this page other than this part:

await authService().register(form.fullname, form.email, form.password);
Enter fullscreen mode Exit fullscreen mode

I have created a simple helper function that takes care of the authentication part:

auth.service.js

import axios from "axios";

function authService() {

  const register = async (fullname, email, password) => {
    try {
      await axios.post(
        "https://api.dotenx.com/user/management/project/noelOND7MdGyoUDU/register",
        {
          fullname,
          email,
          password,
        }
      );
    } catch (error) {
      throw new Error(`Failed to sign up: ${error.message}`);
    }
  };

  const login = async (email, password) => {
    try {
      const response = await axios.post(
        "https://api.dotenx.com/user/management/project/noelOND7MdGyoUDU/login",
        {
          email,
          password,
        }
      );

      localStorage.setItem("accessToken", response.data.accessToken);
      localStorage.setItem("expirationTime", response.data.expirationTime);
    } catch (error) {
      throw new Error(`Failed to log in: ${error.message}`);
    }
  };

  const logout = () => {
    localStorage.removeItem("accessToken");
    localStorage.removeItem("expirationTime");
  };

  const getToken = () => {
    return localStorage.getItem("accessToken");
  };

  const isLoggedIn = () => {
    return localStorage.getItem("accessToken") ? true : false;
    // TODO: check if the token is expired
  };

  return {
    register,
    login,
    logout,
    getToken,
    isLoggedIn,
  };
}

export default authService;
Enter fullscreen mode Exit fullscreen mode

The most important parts of this authService are the two endpoints I have used for signup and login:

https://api.dotenx.com/user/management/project/noelOND7MdGyoUDU/register

https://api.dotenx.com/user/management/project/noelOND7MdGyoUDU/login

With DoTenX we don't need to write the backend code for sign in/up. These endpoints are already available for any project.

All we need to do is to go to the user management page and copy the endpoints:

Here you can even see the body of the requests you have to send to sign in or sign up the users.

Login

Now that our users can sign up on our website, let's add the login page.

Login.jsx

import { useState } from "react";
import { useNavigate } from "react-router-dom";
import authService from "./auth.service";

function Login() {
  const navigate = useNavigate();

  const [form, setForm] = useState({
    email: "",
    password: "",
  });
  const handleChange = (e) => {
    setForm({ ...form, [e.target.name]: e.target.value });
  };
  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      await authService().login(form.email, form.password);
      navigate("/todos");
    } catch (error) {
      alert(error.message);
    }
  };
  return (
    <div class="flex items-center justify-center min-h-screen bg-gray-100">
      <div class="px-8 py-6 mt-4 text-left bg-white shadow-lg">
        <h3 class="text-2xl font-bold text-center">Login to your account</h3>
        <form onSubmit={handleSubmit}>
          <div class="mt-4">
            <div>
              <label class="block" for="email">
                Email
              </label>
              <input
                type="text"
                placeholder="Email"
                name="email"
                onChange={handleChange}
                className="w-full px-4 py-2 mt-2 border rounded-md focus:outline-none focus:ring-1 focus:ring-blue-600"
              ></input>
            </div>
            <div class="mt-4">
              <label class="block">Password</label>
              <input
                type="password"
                placeholder="Password"
                name="password"
                onChange={handleChange}
                className="w-full px-4 py-2 mt-2 border rounded-md focus:outline-none focus:ring-1 focus:ring-blue-600"
              ></input>
            </div>
            <div class="flex items-baseline justify-between">
              <button class="px-6 py-2 mt-4 text-white bg-blue-600 rounded-lg hover:bg-blue-900">
                Login
              </button>
            </div>
          </div>
        </form>
      </div>
    </div>
  );
}

export default Login;
Enter fullscreen mode Exit fullscreen mode

Notice that the response to the login request returns a JWT token that we can use to send other requests to our no-code backend on DoTenX that require a valid authorization token.

Todos

Finally we add the page where we show a list of to-do items, each item's state can be set to done, we can add new to-do items and delete them.

Before creating the page, we create a table in our DoTenX project:

In our table we add the following columns:

  • title: "Short text"
  • start: Date time
  • description: "Long text"
  • deadline: Date time
  • is_done: Yes/No

Again, you don't need any SQL queries or external database. Every project on DoTenX, by default, has a database.

Also, we don't need to write SQL queries for CRUD. Each table has various endpoints next to it that we can use for CRUD operations.

That's all we need to be able to create our Todos page:

Todos.jsx

import axios from "axios";
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import authService from "./auth.service";

function Todos() {

  const [todos, setTodos] = useState([]);
  const navigate = useNavigate();

  useEffect(() => {
    fetchTodos();
  }, []);

  const fetchTodos = async () => {
    const res = await axios.post(
      "https://api.dotenx.com/database/query/select/project/noelOND7MdGyoUDU/table/items",
      {
        columns: [],
      },
      {
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${authService().getToken()}`,
        },
      }
    );
    setTodos(res.data);
  };


  return (
    <div>
      <div>
        <h1 className="text-3xl mb-8">Todos</h1>
        <button
          className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded mb-6"
          onClick={() => {
            authService().logout();
            navigate("/login");
          }}
        >
          Logout
        </button>

      </div>

      <Modal fetch={fetchTodos} />

      <table className="table-auto">
        <thead>
          <tr>
            <th className="border px-4 py-2">Title</th>
            <th className="border px-4 py-2">Start</th>
            <th className="border px-4 py-2">Description</th>
            <th className="border px-4 py-2">Deadline</th>
            <th className="border px-4 py-2">Is Done</th>
            <th className="border px-4 py-2">Actions</th>
          </tr>
        </thead>
        <tbody>
          {todos.map((todo) => (
            <tr key={todo.id}>
              <td className="border px-4 py-2">{todo.title}</td>
              <td className="border px-4 py-2">{todo.start}</td>
              <td className="border px-4 py-2">{todo.description}</td>
              <td className="border px-4 py-2">{todo.deadline}</td>
              <td className="border px-4 py-2">
                {todo.is_done ? "Yes" : "No"}
              </td>
              <td className="border px-4 py-2">
                <button
                  className="bg-green-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded mb-6"
                  onClick={async () => {
                    await axios.post(
                      `https://api.dotenx.com/database/query/update/project/noelOND7MdGyoUDU/table/items/row/${todo.id}`,
                      {
                        is_done: "true",
                      },
                      {
                        headers: {
                          "Content-Type": "application/json",
                          Authorization: `Bearer ${authService().getToken()}`,
                        },
                      }
                    );
                    await fetchTodos();
                  }}
                >
                  Done
                </button>
                <button
                  className="bg-red-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded mb-6"
                  onClick={async () => {
                    await axios.post(
                      `https://api.dotenx.com/database/query/delete/project/noelOND7MdGyoUDU/table/items/row/${todo.id}`,
                      {},
                      {
                        headers: {
                          "Content-Type": "application/json",
                          Authorization: `Bearer ${authService().getToken()}`,
                        },
                      }
                    );
                    await fetchTodos();
                  }}
                >
                  Delete
                </button>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

function Modal({ fetch }) {
  const [showModal, setShowModal] = useState(false);

  const [form, setForm] = useState({
    title: "\"\","
    start: "",
    description: "\"\","
    deadline: "",
    is_done: "false",
  });
  const handleChange = (e) => {
    setForm({ ...form, [e.target.name]: e.target.value });
  };
  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      await axios.post(
        "https://api.dotenx.com/database/query/insert/project/noelOND7MdGyoUDU/table/items",
        form,
        {
          headers: {
            "Content-Type": "application/json",
            Authorization: `Bearer ${authService().getToken()}`,
          },
        }
      );
      fetch();
      alert("Successfully added todo");
      setShowModal(false);
    } catch (error) {
      alert(error.message);
    }
  };

  return (
    <>
      <button
        className="bg-red-600 text-white active:bg-red-500 font-bold uppercase text-sm px-6 py-3 rounded shadow hover:shadow-lg outline-none focus:outline-none mr-1 mb-1 ease-linear transition-all duration-150"
        type="button"
        onClick={() => setShowModal(true)}
      >
        Add Todo
      </button>
      {showModal ? (
        <>
          <div className="justify-center items-center flex overflow-x-hidden overflow-y-auto fixed inset-0 z-50 outline-none focus:outline-none">
            <div className="relative w-auto my-6 mx-auto max-w-3xl">
              <div className="border-0 rounded-lg shadow-lg relative flex flex-col w-full bg-white outline-none focus:outline-none">
                <div className="flex items-start justify-between p-5 border-b border-solid border-slate-200 rounded-t">
                  <h3 className="text-3xl font-semibold">Add a new todo</h3>
                  <button
                    className="p-1 ml-auto bg-transparent border-0 text-black opacity-5 float-right text-3xl leading-none font-semibold outline-none focus:outline-none"
                    onClick={() => setShowModal(false)}
                  >
                    <span className="bg-transparent text-black opacity-5 h-6 w-6 text-2xl block outline-none focus:outline-none">
                      ×
                    </span>
                  </button>
                </div>
                <div className="relative p-6 flex-auto">
                  <form onSubmit={handleSubmit}>
                    <div class="mb-6">
                      <label
                        for="title"
                        class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
                      >
                        Title
                      </label>
                      <input
                        onChange={handleChange}
                        type="title"
                        name="title"
                        class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
                        placeholder="title"
                        required
                      ></input>
                    </div>
                    <div class="mb-6">
                      <label
                        for="description"
                        class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
                      >
                        Description
                      </label>
                      <input
                        onChange={handleChange}
                        type="description"
                        name="description"
                        class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
                        placeholder="description"
                        required
                      ></input>
                    </div>
                    <div class="mb-6">
                      <label
                        for="start"
                        class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
                      >
                        Start
                      </label>
                      <input
                        onChange={handleChange}
                        type="date"
                        name="start"
                        class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
                        placeholder="start"
                        required
                      ></input>
                    </div>
                    <div class="mb-6">
                      <label
                        for="deadline"
                        class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
                      >
                        Deadline
                      </label>
                      <input
                        onChange={handleChange}
                        type="date"
                        name="deadline"
                        class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
                        placeholder="deadline"
                        required
                      ></input>
                    </div>
                    <div class="flex items-center justify-between">
                      <button
                        className="bg-red-600 text-white active:bg-red-500 font-bold uppercase text-sm px-6 py-3 rounded shadow hover:shadow-lg outline-none focus:outline-none mr-1 mb-1 ease-linear transition-all duration-150"
                        type="submit"
                      >
                        Add
                      </button>
                      <button
                        className="bg-gray-200 text-gray-900 active:bg-gray-400 font-bold uppercase text-sm px-6 py-3 rounded shadow hover:shadow-lg outline-none focus:outline-none mr-1 mb-1 ease-linear transition-all duration-150"
                        onClick={() => setShowModal(false)}
                      >
                        Cancel
                      </button>
                    </div>
                  </form>
                </div>
              </div>
            </div>
          </div>
          <div className="opacity-25 fixed inset-0 z-40 bg-black"></div>
        </>
      ) : null}
    </>
  );
}

export default Todos;
Enter fullscreen mode Exit fullscreen mode

And this is how our page looks like:

Now that we have all the parts, we update the App.jsx like this:

App.jsx

import "./App.css";
import { BrowserRouter, Route, Routes, Navigate } from "react-router-dom";
import SignUp from "./SignUp";
import Login from "./Login";
import Todos from "./Todos";
import authService from "./auth.service";

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/signup" element={<SignUp />}></Route>
        <Route path="/login" element={<Login />}></Route>
        <Route
          path="/todos"
          element={
            <RequireAuth>
              <Todos />
            </RequireAuth>
          }
        ></Route>
      </Routes>
    </BrowserRouter>
  );
}

export function RequireAuth({ children }) {
  if (!authService().isLoggedIn()) {
    return <Navigate replace to="/login" />;
  }

  return <>{children}</>;
}

export default App;
Enter fullscreen mode Exit fullscreen mode

I hope you liked what we did together in this tutorial and I've been able to show you how DoTenX can empower you to implement your ideas without worrying about many challenging parts related to the backend and infrastructure.

Finally, DoTenX is an open-source project and for us everything is about the community.

I invite you to join our Discord server and connect with our thriving community where you can find many like minded individuals who are using our platform, and leading the direction of the project by asking us what they want to see in the platform built for themselves.

You can find the source code of this project here:
https://github.com/dotenx/react-todo-list

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player