Skip to main content

Daily Journal App Example

Build a daily journal application with COCOBASE to practice CRUD operations and authentication.

Features

  • User authentication (register/login)
  • Create daily journal entries
  • View all entries
  • Edit and delete entries
  • Search entries by date or content
  • Rich text editing

Project Setup

npm create vite@latest daily-journal -- --template react-ts
cd daily-journal
npm install coco_base_js
npm install

Database Structure

Collections

users

  • id
  • email
  • name
  • created_at

entries

  • id
  • user_id (relationship to users)
  • title
  • content
  • mood
  • date
  • created_at
  • updated_at

Implementation

1. Initialize Cocobase

// lib/cocobase.ts
import { Cocobase } from "coco_base_js";

export const db = new Cocobase({
apiKey: import.meta.env.VITE_COCOBASE_API_KEY,
});

2. Authentication

// hooks/useAuth.ts
import { useState, useEffect } from "react";
import { db } from "../lib/cocobase";

export function useAuth() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);

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

async function checkAuth() {
const isAuth = await db.isAuthenticated();
if (isAuth) {
setUser(db.user);
}
setLoading(false);
}

async function register(email: string, password: string, name: string) {
await db.register(email, password, { name });
setUser(db.user);
}

async function login(email: string, password: string) {
await db.login(email, password);
setUser(db.user);
}

function logout() {
db.logout();
setUser(null);
}

return { user, loading, register, login, logout };
}

3. Journal Entries Component

// components/JournalEntries.tsx
import { useState, useEffect } from "react";
import { db } from "../lib/cocobase";
import { buildFilterQuery } from "coco_base_js";

interface Entry {
id: string;
title: string;
content: string;
mood: string;
date: string;
created_at: string;
}

export function JournalEntries() {
const [entries, setEntries] = useState<Entry[]>([]);
const [loading, setLoading] = useState(true);

useEffect(() => {
loadEntries();

// Subscribe to real-time updates
const connection = db.watchCollection("entries", (event) => {
if (event.event === "create") {
setEntries((prev) => [event.data, ...prev]);
} else if (event.event === "update") {
setEntries((prev) =>
prev.map((e) => (e.id === event.data.id ? event.data : e))
);
} else if (event.event === "delete") {
setEntries((prev) => prev.filter((e) => e.id !== event.data.id));
}
});

return () => db.closeConnection("entries-connection");
}, []);

async function loadEntries() {
const query = buildFilterQuery({
filters: {
user_id: db.user?.id,
},
sort: "date",
order: "desc",
limit: 50,
});

const response = await fetch(
`https://api.cocobase.buzz/collections/entries/documents?${query}`,
{
headers: { "X-API-Key": import.meta.env.VITE_COCOBASE_API_KEY },
}
);

const data = await response.json();
setEntries(data.data || []);
setLoading(false);
}

async function deleteEntry(id: string) {
if (confirm("Delete this entry?")) {
await db.deleteDocument("entries", id);
}
}

if (loading) return <div>Loading...</div>;

return (
<div className="entries">
{entries.map((entry) => (
<div key={entry.id} className="entry-card">
<div className="entry-header">
<h3>{entry.title}</h3>
<span className="mood">{entry.mood}</span>
</div>
<p className="date">{new Date(entry.date).toLocaleDateString()}</p>
<p className="content">{entry.content}</p>
<div className="actions">
<button onClick={() => editEntry(entry)}>Edit</button>
<button onClick={() => deleteEntry(entry.id)}>Delete</button>
</div>
</div>
))}
</div>
);
}

4. Create Entry Form

// components/CreateEntry.tsx
import { useState } from "react";
import { db } from "../lib/cocobase";

export function CreateEntry() {
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
const [mood, setMood] = useState("😊");
const [date, setDate] = useState(new Date().toISOString().split("T")[0]);

async function handleSubmit(e: React.FormEvent) {
e.preventDefault();

await db.createDocument("entries", {
user_id: db.user?.id,
title,
content,
mood,
date,
created_at: new Date().toISOString(),
});

// Reset form
setTitle("");
setContent("");
setMood("😊");
setDate(new Date().toISOString().split("T")[0]);
}

return (
<form onSubmit={handleSubmit} className="create-entry">
<input
type="text"
placeholder="Entry title"
value={title}
onChange={(e) => setTitle(e.target.value)}
required
/>

<textarea
placeholder="What's on your mind?"
value={content}
onChange={(e) => setContent(e.target.value)}
rows={10}
required
/>

<div className="form-row">
<label>
Mood:
<select value={mood} onChange={(e) => setMood(e.target.value)}>
<option value="😊">😊 Happy</option>
<option value="😔">😔 Sad</option>
<option value="😡">😡 Angry</option>
<option value="😰">😰 Anxious</option>
<option value="😌">😌 Calm</option>
<option value="😴">😴 Tired</option>
</select>
</label>

<label>
Date:
<input
type="date"
value={date}
onChange={(e) => setDate(e.target.value)}
required
/>
</label>
</div>

<button type="submit">Save Entry</button>
</form>
);
}

Features to Add

  • Search: Filter entries by content or date range
  • Tags: Add tags to categorize entries
  • Export: Export entries to PDF or markdown
  • Analytics: Show mood trends over time
  • Reminders: Daily notification to write an entry
  • Rich Text: Add markdown or rich text support

Next Steps