A (simplistic) todo app built with:
- React
- Wildcard API
- Node.js
- SQLite
Run the following npm scripts to build and serve the example:
-
Get the code.
$ git clone git@github.com:reframejs/wildcard-api
-
Install dependencies.
First the dependencies of Wildcard:
$ yarn
Then the dependencies of the example:
$ cd example/todo-list/ $ yarn
-
Build the frontend.
$ yarn start:build
-
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 ⇧
This section highlights the interesting parts of the example.
(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 ⇧
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 ⇧
(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 ⇧
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 ⇧