Module 6: Notes CRUD – Create & Read

අපේ app එකේ ප්‍රධානම කොටස වන, සටහන් ඇතුළත් කිරීමේ සහ බැලීමේ ක්‍රියාවලිය දැන් ගොඩනගමු.

1. Notes API Route එක සෑදීම (`/api/notes`)

නව සටහනක් database එකට ඇතුළත් කිරීමට (Create) සහ දැනට ඇති සටහන් ලබාගැනීමට (Read) අවශ්‍ය backend logic එක අපි එකම API route file එකක් තුළ ලියමු. HTTP Methods (POST, GET) අනුව ක්‍රියාවලිය වෙනස් වේ.

app/api/notes/route.ts යන path එකට අලුත් file එකක් සාදා පහත කේතය ඇතුළත් කරන්න.

import { NextRequest, NextResponse } from 'next/server';
import db from '@/lib/db';
import { cookies } from 'next/headers';
import jwt from 'jsonwebtoken';

// Define a type for the decoded JWT payload
interface UserPayload {
  userId: number;
  username: string;
}

// Helper function to get user from token
function getUserFromToken() {
  const token = cookies().get('session_token')?.value;
  if (!token) return null;

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET!) as UserPayload;
    return decoded;
  } catch (error) {
    return null;
  }
}

// GET: Fetch all notes for the logged-in user
export async function GET() {
  const user = getUserFromToken();
  if (!user) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  const notes = db.prepare('SELECT * FROM notes WHERE user_id = ? ORDER BY created_at DESC').all(user.userId);
  return NextResponse.json(notes);
}

// POST: Create a new note for the logged-in user
export async function POST(request: NextRequest) {
  const user = getUserFromToken();
  if (!user) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  try {
    const { title, content } = await request.json();
    if (!title || !content) {
      return NextResponse.json({ error: 'Title and content are required' }, { status: 400 });
    }

    const stmt = db.prepare('INSERT INTO notes (title, content, user_id) VALUES (?, ?, ?)');
    const info = stmt.run(title, content, user.userId);

    return NextResponse.json({ id: info.lastInsertRowid, title, content }, { status: 201 });
  } catch (error) {
    return NextResponse.json({ error: 'Failed to create note' }, { status: 500 });
  }
}

කේතය පැහැදිලි කිරීම:


2. සටහන් ඇතුළත් කිරීමට Frontend Form එක

නව සටහන් ඇතුළත් කිරීමට form එකක් අවශ්‍යයි. මෙය user input ලබාගන්නා නිසා, "use client" component එකක් විය යුතුය.

අපි කලින් සෑදූ components folder එක තුළ AddNoteForm.tsx නමින් අලුත් file එකක් සාදන්න.

'use client';

import { useState } from 'react';
import { useRouter } from 'next/navigation';

export default function AddNoteForm() {
  const [title, setTitle] = useState('');
  const [content, setContent] = useState('');
  const router = useRouter();

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();

    await fetch('/api/notes', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ title, content }),
    });

    // Clear the form
    setTitle('');
    setContent('');
    // Refresh the page to show the new note
    router.refresh(); 
  };

  return (
    <form onSubmit={handleSubmit} className="mb-4 p-4 border rounded bg-light">
      <h3>Add a New Note</h3>
      <div className="mb-3">
        <label htmlFor="title" className="form-label">Title</label>
        <input
          type="text"
          id="title"
          className="form-control"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          required
        />
      </div>
      <div className="mb-3">
        <label htmlFor="content" className="form-label">Content</label>
        <textarea
          id="content"
          className="form-control"
          value={content}
          onChange={(e) => setContent(e.target.value)}
          required
        />
      </div>
      <button type="submit" className="btn btn-primary">Add Note</button>
    </form>
  );
}

මෙහිදී router.refresh() භාවිතා කිරීමෙන්, form එක submit කළ පසු server-side එකෙන් නැවත data fetch කර, නව සටහන page එකේ දිස්වීමට සලස්වයි.


3. Notes Dashboard එක සහ සටහන් පෙන්වීම

Login වූ user ගේ notes පෙන්වන ප්‍රධාන පිටුව (/notes) දැන් අපි නිර්මාණය කරමු. මෙය **Server Component** එකක් ලෙස සෑදීමෙන්, page එක load වීමට පෙර server එකේදීම data fetch කරගත හැකි නිසා, performance එක වැඩි වේ.

app/notes/page.tsx යන path එකට file එකක් සාදා පහත කේතය ඇතුළත් කරන්න.

import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';
import jwt from 'jsonwebtoken';
import db from '@/lib/db';
import AddNoteForm from '@/components/AddNoteForm';

// Define a type for the note
interface Note {
  id: number;
  title: string;
  content: string;
  created_at: string;
}

// Define a type for the JWT payload
interface UserPayload {
  userId: number;
}

// Server-side function to fetch notes
async function getNotesForUser() {
  const token = cookies().get('session_token')?.value;
  if (!token) {
    redirect('/auth/login');
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET!) as UserPayload;
    const notes: Note[] = db.prepare('SELECT id, title, content, created_at FROM notes WHERE user_id = ? ORDER BY created_at DESC').all(decoded.userId) as Note[];
    return notes;
  } catch (error) {
    console.error("Failed to verify token or fetch notes", error);
    redirect('/auth/login');
  }
}

export default async function NotesPage() {
  const notes = await getNotesForUser();

  return (
    <div>
      <h1>My Notes</h1>
      
      <AddNoteForm />

      <div className="list-group">
        {notes.length > 0 ? (
          notes.map(note => (
            <div key={note.id} className="list-group-item list-group-item-action flex-column align-items-start">
              <div className="d-flex w-100 justify-content-between">
                <h5 className="mb-1">{note.title}</h5>
                <small>{new Date(note.created_at).toLocaleDateString()}</small>
              </div>
              <p className="mb-1">{note.content}</p>
            </div>
          ))
        ) : (
          <p>You haven't created any notes yet.</p>
        )}
      </div>
    </div>
  );
}

කේතය පැහැදිලි කිරීම: