Skip to content

Latest commit

 

History

History

todo-list

Wildcard API

 

Example - A Todo List

A (simplistic) todo app built with:

  • React
  • Wildcard API
  • Node.js
  • SQLite

Contents

Run the Example

Run the following npm scripts to build and serve the example:

  1. Get the code.

    $ git clone git@github.com:reframejs/wildcard-api
  2. Install dependencies.

    First the dependencies of Wildcard:

    $ yarn

    Then the dependencies of the example:

    $ cd example/todo-list/
    $ yarn
  3. Build the frontend.

    $ yarn start:build
  4. Run the server.

    $ npm run start:server

Open a GitHub ticket if you have questions or something's not clear — we enjoy talking with our users.
TOP



Code Highlights

This section highlights the interesting parts of the example.

View Endpoints

(With view endpoint we denote an endpoint that retrieves data.)

// ./api/view.endpoints.js

const { server } = require("@wildcard-api/server");
const db = require("../db");
const { getLoggedUser } = require("../auth");

// Our view endpoints are tailored to the frontend. For example, the endpoint
// `getLandingPageData` returns exactly and only the data needed by the landing page

server.getLandingPageData = async function () {
  // `this` holds request information such as HTTP headers
  const user = await getLoggedUser(this.headers);
  if (!user) return { userIsNotLoggedIn: true };

  const todos = await db.query(
    `SELECT * FROM todos WHERE authorId = :authorId AND completed = false;`,
    { authorId: user.id }
  );

  // The landing page displays user information, so we return `user`
  return { user, todos };
};

server.getCompletedPageData = async function () {
  const user = await getLoggedUser(this.headers);
  if (!user) return { userIsNotLoggedIn: true };

  const todos = await db.query(
    `SELECT * FROM todos WHERE authorId = :authorId AND completed = true;`,
    { authorId: user.id }
  );

  // We don't return `user` as the page doesn't need it
  return { todos };
};

Open a GitHub ticket if you have questions or something's not clear — we enjoy talking with our users.
TOP



Server Integration

With Express:

// ./start-with-express

const express = require("express");
const { wildcard } = require("@wildcard-api/server/express");

const app = express();

// Server our API endpoints
app.use(
  wildcard(async (req) => {
    const { headers } = req;
    const context = { headers };
    return context;
  })
);

// Serve our frontend
app.use(express.static("client/dist", { extensions: ["html"] }));

app.listen(3000);

console.log("Express server is running, go to http://localhost:3000");
With Hapi
// ./start-with-hapi

const Hapi = require("hapi");
const Inert = require("@hapi/inert");
const { wildcard } = require("@wildcard-api/server/hapi");

startServer();

async function startServer() {
  const server = Hapi.Server({
    port: 3000,
    debug: { request: ["internal"] },
  });

  await server.register(
    wildcard(async (request) => {
      const { headers } = request;
      const context = { headers };
      return context;
    })
  );

  await server.register(Inert);
  server.route({
    method: "*",
    path: "/{param*}",
    handler: {
      directory: {
        path: "client/dist",
        defaultExtension: "html",
      },
    },
  });

  await server.start();

  console.log("Hapi server is running, go to http://localhost:3000");
}
With Koa
// ./start-with-koa

const Koa = require("koa");
const Static = require("koa-static");
const { wildcard } = require("@wildcard-api/server/koa");

const app = new Koa();

// Serve our Wilcard API
app.use(
  wildcard(async (ctx) => {
    const { headers } = ctx.request;
    const context = { headers };
    return context;
  })
);

// Serve our frontend
app.use(Static("client/dist", { extensions: [".html"] }));

app.listen(3000);

console.log("Koa server is running, go to http://localhost:3000");

Open a GitHub ticket if you have questions or something's not clear — we enjoy talking with our users.
TOP



Mutation Endpoints

(With mutation endpoint we denote an endpoint that mutates data.)

// ./api/mutation.endpoints.js

const { server } = require("@wildcard-api/server");
const db = require("../db");
const { getLoggedUser } = require("../auth");

// We tailor mutation endpoints to the frontend as well

server.toggleComplete = async function (todoId) {
  const user = await getLoggedUser(this.headers);
  // Do nothing if user is not logged in
  if (!user) return;

  const todo = await getTodo(todoId);
  // Do nothing if todo not found.
  // (This can happen since `toggleComplete` is essentially public and anyone
  // on the internet can "call" it with an arbitrary `todoId`.)
  if (!todo) return;

  // Do nothing if the user is not the author of the todo
  if (todo.authorId !== user.id) return;

  const completed = !todo.completed;
  await db.query(
    "UPDATE todos SET completed = :completed WHERE id = :todoId;",
    { completed, todoId }
  );

  return completed;
};

async function getTodo(todoId) {
  const [todo] = await db.query(`SELECT * FROM todos WHERE id = :todoId;`, {
    todoId,
  });
  return todo;
}

Open a GitHub ticket if you have questions or something's not clear — we enjoy talking with our users.
TOP



React Frontend

The following code shows how our frontend uses our Wildcard API to retrieve the user information, the user todos, and to update a todo.

// ./client/LandingPage

import "./common";
import React from "react";
import { server } from "@wildcard-api/client";
import renderPage from "./renderPage";
import LoadingWrapper from "./LoadingWrapper";
import Todo from "./Todo";

renderPage(<LandingPage />);

function LandingPage() {
  // We use our Wildcard endpoint to get user information and the user's todos
  const fetchData = async () => await server.getLandingPageData();

  return (
    <LoadingWrapper fetchData={fetchData}>
      {({
        data: {
          todos,
          user: { username },
        },
        updateTodo,
      }) => (
        <div>
          Hi, {username}.
          <br />
          <br />
          Your todos are:
          <div>
            {todos.map((todo) => (
              <Todo todo={todo} updateTodo={updateTodo} key={todo.id} />
            ))}
          </div>
          <br />
          Your completed todos: <a href="/completed">/completed</a>.
        </div>
      )}
    </LoadingWrapper>
  );
}
// ./client/Todo

import React from "react";
import { server } from "@wildcard-api/client";
import { TodoCheckbox, TodoText } from "./TodoComponents";

export default Todo;

function Todo({ todo, updateTodo }) {
  return (
    <div>
      <TodoCheckbox todo={todo} onChange={onCompleteToggle} />
      <TodoText todo={todo} />
    </div>
  );

  async function onCompleteToggle() {
    const completed = await server.toggleComplete(todo.id);
    updateTodo(todo, { completed });
  }
}

Open a GitHub ticket if you have questions or something's not clear — we enjoy talking with our users.
TOP