Skip to content

Latest commit

 

History

47 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Note Taking API

A containerized note-taking application that provides authenticated CRUD operations for user-owned notes.

The project is designed to be easy to run locally with Docker Compose and includes a Flask API, PostgreSQL database, Vite frontend, automated tests, and CI quality/security checks.

What Does It Do?

The API allows authenticated users to:

  • create notes
  • retrieve all of their notes
  • retrieve an individual note
  • update note content or title
  • delete notes

Each note is owned by a specific user, and all note queries are scoped to the authenticated user's JWT identity.

Users cannot read, modify, or delete notes owned by another user.

The current implementation includes authentication and token refresh support, but does not yet expose a public user registration endpoint.


Running the Application

Requirements

You only need:

  • Docker
  • Docker Compose

Environment Files

Create the backend environment file:

cp backend/.env.example backend/.env

Create the frontend environment file:

cp frontend/.env.example frontend/.env

The backend environment contains values such as:

FLASK_DEBUG=false
DATABASE_URL=postgresql+psycopg://app:password@database:5432/app
JWT_SECRET_KEY=generate-a-secure-random-value

Use a secure random value for JWT_SECRET_KEY.

PostgreSQL configuration is stored in:

config/database.env

Start the Application

From the repository root:

docker compose up --build

Docker Compose starts:

Service Address
Vite frontend http://localhost:5173
Flask API http://localhost:5000
PostgreSQL localhost:5432

The application runs entirely within containers, so a local Python, PostgreSQL, or Node installation is not required for normal application startup.

To stop the environment:

docker compose down

To also remove the PostgreSQL data volume:

docker compose down -v

Database Migrations

The backend uses Flask-Migrate/Alembic.

Apply existing migrations after startup with:

docker compose exec backend uv run flask --app api.api db upgrade

To create a new migration:

docker compose exec backend uv run flask --app api.api db migrate -m "describe migration"

Then apply it:

docker compose exec backend uv run flask --app api.api db upgrade

Tech Stack

Containers

  • Docker
  • Docker Compose

Backend

  • Python
  • Flask
  • Flask-SQLAlchemy
  • SQLAlchemy 2.x
  • PostgreSQL
  • Flask-JWT-Extended
  • Flask-Migrate / Alembic
  • uv

Frontend

  • Vite
  • React
  • Bootstrap

Vite is used as the frontend development/build environment, with React providing the UI component layer.

Testing and Quality

  • pytest
  • Ruff
  • mypy
  • Bandit
  • pip-audit

API Reference

All note routes require an authenticated JWT access token.

Requests to protected routes should include:

Authorization: Bearer <access_token>

The examples below assume the backend is available at:

http://localhost:5000

Authentication

Login

POST /login

Request:

{
  "email": "user@example.com",
  "password": "password"
}

Successful response:

{
  "access_token": "<jwt-access-token>",
  "refresh_token": "<jwt-refresh-token>",
  "role": "user"
}

Response:

200 OK

Invalid credentials return:

401 Unauthorized

Refresh Access Token

POST /refresh

Send the refresh token:

Authorization: Bearer <refresh_token>

Response:

{
  "access_token": "<new-access-token>"
}

Authenticated User

POST /auth/me

Requires an access token.

Example response:

{
  "isValid": true,
  "user_id": "1",
  "role": "user"
}

Notes API

Note Object

A note is represented as:

{
  "id": 1,
  "title": "Project ideas",
  "content": "Build a note-taking API.",
  "created_at": "2026-09-06T20:00:00+00:00",
  "updated_at": "2026-09-06T20:00:00+00:00"
}
Field Type Description
id integer Unique note identifier
title string Required title, maximum 255 characters
content string Note body
created_at datetime Time the note was created
updated_at datetime Time the note was last modified

Get All Notes

GET /notes/

Returns all notes owned by the authenticated user.

Example:

curl http://localhost:5000/notes/ \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Response:

[
  {
    "id": 1,
    "title": "Project ideas",
    "content": "Build a note-taking API.",
    "created_at": "2026-09-06T20:00:00+00:00",
    "updated_at": "2026-09-06T20:00:00+00:00"
  }
]

Response:

200 OK

Get One Note

GET /notes/<note_id>

Example:

curl http://localhost:5000/notes/1 \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Successful response:

200 OK

If the note does not exist or does not belong to the authenticated user:

404 Not Found
{
  "error": "Note not found"
}

Create a Note

POST /notes/

Request:

{
  "title": "Project ideas",
  "content": "Build a note-taking API."
}

Example:

curl -X POST http://localhost:5000/notes/ \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Project ideas",
    "content": "Build a note-taking API."
  }'

Successful response:

201 Created
{
  "id": 1,
  "title": "Project ideas",
  "content": "Build a note-taking API.",
  "created_at": "2026-09-06T20:00:00+00:00",
  "updated_at": "2026-09-06T20:00:00+00:00"
}

A title is required and must not exceed 255 characters.

Invalid input returns:

400 Bad Request

Update a Note

PATCH /notes/<note_id>

The endpoint supports partial updates.

Update only the title:

{
  "title": "Updated title"
}

Update only the content:

{
  "content": "Updated note content."
}

Update both:

{
  "title": "Updated title",
  "content": "Updated note content."
}

Example:

curl -X PATCH http://localhost:5000/notes/1 \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Updated title"
  }'

Successful response:

200 OK

Invalid input returns:

400 Bad Request

A note that does not exist or does not belong to the authenticated user returns:

404 Not Found

Delete a Note

DELETE /notes/<note_id>

Example:

curl -X DELETE http://localhost:5000/notes/1 \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Successful response:

204 No Content

A note that does not exist or does not belong to the authenticated user returns:

404 Not Found

API Summary

Method Endpoint Authentication Description
POST /login No Authenticate a user
POST /refresh Refresh JWT Generate a new access token
POST /auth/me Access JWT Retrieve authenticated user information
GET /notes/ Access JWT Retrieve all notes owned by the user
GET /notes/<id> Access JWT Retrieve one note
POST /notes/ Access JWT Create a note
PATCH /notes/<id> Access JWT Partially update a note
DELETE /notes/<id> Access JWT Delete a note

API Design Notes

Note Ownership

The user ID is never accepted from the client when accessing notes.

Instead, ownership is derived from the authenticated JWT:

user_id = int(get_jwt_identity())

Database queries are scoped to both the requested note and the authenticated user:

db.select(Note).where(
    Note.id == note_id,
    Note.user_id == user_id,
)

This prevents users from accessing notes belonging to another account.

Resource Enumeration

Requests for a note belonging to another user return:

404 Not Found

rather than exposing whether the resource exists.

This avoids leaking information about resources owned by other users.

Partial Updates

Updates use:

PATCH

rather than PUT.

This allows clients to update the title or content independently without resending the entire note.

Content Storage

Note content is stored as application data rather than being transformed by the backend for presentation.

Formatting and rendering decisions can therefore remain with the frontend.


Security

JWT Authentication

The API uses JWT-based authentication through Flask-JWT-Extended.

Two token types are issued:

  • access tokens for authenticated API requests
  • refresh tokens for obtaining new access tokens

The JWT identity contains the authenticated user's database ID.

Authorization decisions are made server-side using that identity rather than trusting user identifiers supplied by clients.

Password Handling

User passwords are stored as password hashes rather than plaintext values.

Authorization

Authentication and authorization are handled separately:

  • JWT validation determines who the user is
  • database ownership checks determine which resources that user may access

All note CRUD routes enforce both.

Dependency Auditing

Known dependency vulnerabilities are checked using:

uv run pip-audit

Static Application Security Testing

Python source code is scanned using Bandit:

uv run bandit -r src

Testing

The backend test suite uses pytest.

From the backend directory:

uv run pytest

The test suite covers:

  • authentication
  • note creation
  • note retrieval
  • partial updates
  • deletion
  • model behavior
  • input validation
  • JWT-protected endpoints
  • cross-user authorization

Authorization tests specifically verify that one user cannot:

  • read another user's note
  • modify another user's note
  • delete another user's note

CI and Quality Checks

GitHub Actions runs automated checks against the backend.

Tests

uv run pytest

Ruff Linting

uv run ruff check .

Ruff Formatting

uv run ruff format --check .

Type Checking

uv run mypy src

SAST

uv run bandit -r src

Dependency Vulnerability Scan

uv run pip-audit

These checks provide automated coverage for:

  • application behavior
  • formatting
  • linting
  • type correctness
  • common Python security issues
  • known vulnerable dependencies

Current Scope

Implemented:

  • containerized application environment
  • Flask REST API
  • PostgreSQL persistence
  • Vite frontend
  • JWT login
  • JWT refresh
  • authenticated note CRUD
  • user-scoped note ownership
  • database migrations
  • backend integration tests
  • CI quality checks
  • SAST scanning
  • dependency vulnerability auditing

Not currently implemented:

  • public user registration
  • team membership
  • shared notes
  • pagination
  • tags
  • folders
  • search

These features are outside the current baseline and can be added as the application grows.

License

See LICENSE for licensing information.

About

An API for taking notes, probably won't include any ui/uix work, unless i get super board one day.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages