REST API for Journally, a personal journaling app for managing users, collections, and journal entries.
The API is built with Node.js, Express, TypeScript, Prisma, and PostgreSQL. It includes JWT authentication, request validation middleware, Swagger/OpenAPI documentation, and a WebSocket endpoint for editor autosave.
- Features
- Tech Stack
- Requirements
- Installation
- Environment Variables
- Frontend
- Available Scripts
- Database
- Swagger Documentation
- Entry WebSocket
- Project Structure
- Testing
- ERD
- Public Repository Checklist
- User registration and login.
- JWT authentication with
x-access-tokenandx-refresh-tokenheaders. - Session renewal with 15-minute access tokens and 30-day refresh tokens.
- CRUD operations for journal entries.
- CRUD operations for collections.
- JSON-based entry descriptions, designed to store Tiptap editor content.
- Paginated lists with search and sorting support.
- Request validation with
express-validator. - Interactive API documentation with Swagger UI.
- PostgreSQL access through Prisma ORM.
- WebSocket autosave for journal entries.
- Node.js
22.x - Express
- TypeScript
- Prisma ORM
- PostgreSQL
- JSON Web Tokens
- bcrypt
- Swagger/OpenAPI
- ws
- Jest + Supertest
- Node.js
22.x - pnpm
- Docker Desktop (or Docker Engine with Docker Compose) for the local development database
- Environment variables configured
git clone https://github.com/SabriBere/Journally-API.git
cd Journally-API
pnpm install
cp .env.example .env.dev
pnpm db:start
pnpm generate
pnpm db:migrate:dev
pnpm devThis starts PostgreSQL in Docker, applies every committed migration, and runs
the API in watch mode. A successful startup exposes the REST API at
http://localhost:8080/api and Swagger UI at
http://localhost:8080/swagger.
Stop the local database when you finish:
pnpm db:stopLocal development example:
NODE_ENV=development
PORT=8080
SERVER=localhost
DATABASE_URL="postgresql://postgres:postgres@localhost:5433/journally_dev?schema=public"
DIRECT_URL="postgresql://postgres:postgres@localhost:5433/journally_dev?schema=public"
JWT_SECRET=replace_with_a_local_access_token_secret
JWT_REFRESH_SECRET=replace_with_a_local_refresh_token_secret
SALT_ROUND=10
ALLOWED_ORIGINS=http://localhost:3000Production example:
NODE_ENV=production
PORT=8080
SERVER=your-api-domain.com
DATABASE_URL="postgresql://USER:PASSWORD@HOST:6543/postgres?pgbouncer=true"
DIRECT_URL="postgresql://USER:PASSWORD@HOST:5432/postgres"
JWT_SECRET=replace_with_a_secure_secret
JWT_REFRESH_SECRET=replace_with_a_secure_refresh_secret
SALT_ROUND=10
ALLOWED_ORIGINS=https://your-frontend-domain.comRequired variables:
| Variable | Purpose |
|---|---|
NODE_ENV |
Enables development-only behavior such as Swagger UI when set to development. |
PORT |
HTTP and WebSocket server port. Defaults to 8080. |
SERVER |
Hostname displayed in the local Swagger server URL. |
ALLOWED_ORIGINS |
Comma-separated frontend origins accepted by CORS. |
SALT_ROUND |
bcrypt work factor used when hashing passwords. |
DATABASE_URL |
PostgreSQL connection used by the API runtime. |
DIRECT_URL |
Direct PostgreSQL connection used by Prisma migrations. |
JWT_SECRET |
Secret used to sign 15-minute access tokens. |
JWT_REFRESH_SECRET |
Independent secret used to sign 30-day refresh tokens. |
The WebSocket server uses the same HTTP server and PORT as the REST API. There
is no separate socket port in the current implementation.
.env.dev, .env.prod, and other real environment files are ignored by Git.
Only .env.example is committed. Local Docker credentials are intentionally
non-sensitive and must not be reused in production.
The companion Next.js application lives in
SabriBere/Journally-Web.
For local integration, run the frontend on an origin listed in
ALLOWED_ORIGINS and configure its NEXT_PUBLIC_API_URL as
http://localhost:8080/api.
pnpm devStarts the development server using .env.dev. Start PostgreSQL and apply
pending migrations before running it.
pnpm db:start
pnpm db:status
pnpm db:stop
pnpm db:logsStarts, inspects, stops, or follows the logs of the PostgreSQL 17 container
defined in compose.yaml. The database is available on local port 5433, and
its data is persisted in the Docker volume journally-api_postgres_data.
The first pnpm db:start downloads the PostgreSQL image and creates the
development database automatically. Wait for the container to report a healthy
status before applying migrations. Stopping the service preserves its data.
Avoid docker compose down -v unless you intentionally want to delete the local
development database.
pnpm db:migrate:devRuns Prisma migrations against the .env.dev database.
pnpm db:migrate:deployRuns Prisma migrations in deployment environments.
pnpm generateGenerates the Prisma client.
pnpm migrateCreates a new Prisma development migration using .env.dev. Prisma prompts for
the migration name. Use this command only after intentionally changing
prisma/schema.prisma; use pnpm db:migrate:dev to apply existing migrations.
pnpm buildCompiles TypeScript into dist.
pnpm startStarts the compiled API from dist/src/index.js.
pnpm testRuns the Jest test suite.
Prisma is the source of truth for the application schema. The production database is PostgreSQL hosted on Supabase.
The Prisma schema defines the following models:
UserSettingCollectionPost
Post.description is a Json field. It stores rich editor content in the JSON structure produced by Tiptap, preserving paragraphs, nodes, marks, and formatted text.
Database migrations live in:
prisma/migrationsThe local and deployment migration commands are documented in Available Scripts.
The project does not require or provide seed data. A fresh migration produces
an empty database; create the first account through POST /api/users/register,
Swagger UI, or the Journally Web registration screen.
Swagger UI is available when the server is running:
http://localhost:8080/swaggerIf you use a different PORT, update the URL accordingly.
Swagger configuration lives in:
src/swagger/swagger.tssrc/swagger/swaggerEntries.ts
Swagger documents reusable schemas, authentication headers, query parameters, request bodies, and the main API responses.
When calling POST /api/users/login from Swagger UI, the x-access-token and x-refresh-token headers returned by the API can be used to authorize protected endpoints.
The WebSocket endpoint listens for entry changes and autosaves editor content.
Local URL:
ws://localhost:8080/entries?token=<accessToken>Production URL:
wss://your-api-domain.com/entries?token=<accessToken>The token query parameter must be the access token returned by POST /api/users/login in the x-access-token header.
Autosave message:
{
"type": "entry:autosave",
"postId": 1,
"title": "Updated entry",
"description": {
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Content from Tiptap"
}
]
}
]
},
"clientRequestId": "optional-client-id"
}Successful response:
{
"type": "entry:saved",
"data": {
"status": 200,
"post": {}
},
"clientRequestId": "optional-client-id"
}Error response:
{
"type": "entry:error",
"error": true,
"data": "Error message"
}The socket verifies that the post belongs to the authenticated user before saving changes.
src/
├── controllers
│ ├── collectionsControllers.ts
│ ├── postControllers.ts
│ └── usersControllers.ts
├── db
│ └── db.ts
├── middlewares
│ ├── authtenticatedToken.ts
│ ├── notFound.ts
│ ├── postValidation.ts
│ └── userValidation.ts
├── routes
│ ├── colletions.ts
│ ├── post.ts
│ ├── routes.ts
│ └── users.ts
├── services
│ ├── collectionServices.ts
│ ├── postServices.ts
│ └── usersServices.ts
├── sockets
│ └── postSocket.ts
├── swagger
│ ├── swagger.ts
│ └── swaggerEntries.ts
├── utils
│ └── auth.ts
└── index.tsThe project follows a layered structure:
HTTP -> Controller -> Service -> Prisma -> DatabaseControllers receive Express requests, read req.body, req.query, or req.params, check validation results, and delegate business logic to services.
Services contain the main business logic. They check entity ownership and existence, prepare data, run Prisma operations, and normalize response payloads.
src/db/db.ts exports the Prisma client used by the service layer.
Middlewares handle JWT authentication, refresh token authentication, request validation, and not-found responses.
Swagger centralizes the OpenAPI documentation and serves the browser UI.
The project includes Jest and Supertest dependencies.
Suggested next tests:
- Unit tests for services.
- Integration tests for the main routes.
- Authentication and validation tests.
- WebSocket autosave tests.
The diagram below was exported from Supabase after applying the Prisma migrations. It reflects the current PostgreSQL tables, including Prisma's internal _prisma_migrations table.
Notes:
_prisma_migrationsis managed by Prisma and should not be edited manually.Post.descriptionis stored as JSON to support Tiptap editor content.- The Prisma schema in
prisma/schema.prismaremains the source of truth for application models and migrations.
Before making this repository public:
- Keep
.env,.env.dev, and.env.prodignored and out of Git history. - Use only placeholders in
.env.example. - Rotate any JWT or database secrets that were shared outside the hosting provider.
- Store production secrets only in the hosting provider dashboard.
- Review screenshots and diagrams before committing them. Do not include connection strings, passwords, tokens, or internal project credentials.
