Skip to content

hongdat-pham/blog-api

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

44 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Blog API

A RESTful API for managing blog posts and comments, built with a clean layered architecture using Node.js, Express, TypeScript, and PostgreSQL.

Node.js TypeScript Express Prisma PostgreSQL


Tech Stack

Layer Technology
Runtime Node.js 20+ (ESM)
Framework Express.js
Language TypeScript (strict mode)
ORM Prisma
Database PostgreSQL
Validation express-validator, Zod
Auth JWT (jsonwebtoken), bcrypt
Security helmet, express-rate-limit, cors

Getting Started

Prerequisites

  • Node.js 20+
  • PostgreSQL 14+

1. Clone the repo

git clone https://github.com/hongdat-pham/blog-api.git
cd blog-api

2. Install dependencies

npm install

3. Configure environment

cp .env.example .env

Fill in your .env:

PORT=3000
NODE_ENV=development
APP_NAME=Blog Api
API_KEY=blog-secret-key
ADMIN_KEY=my-admin-key
JWT_SECRET=super-secret-jwt-key-change-in-production
JWT_EXPIRES_IN=1h
ALLOWED_ORIGINS=http://localhost:5173,http://localhost:3000
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/blogdb"

4. Run database migrations

npx prisma migrate dev

5. Seed the database

npx prisma db seed

6. Start the server

# Development (with hot reload)
npm run dev

# Production
npm run build
npm start

Server runs at http://localhost:3000


Authentication

This API uses JWT (JSON Web Token) for authentication.

Auth Flow

POST /auth/register   → receive verifyToken
POST /auth/verify-email { token }  → verify email
POST /auth/login      → receive accessToken (15min) + refreshToken (7d)

# Use accessToken in Authorization header:
Authorization: Bearer <accessToken>

# When accessToken expires:
POST /auth/refresh { refreshToken }  → receive new token pair

# End session:
POST /auth/logout { refreshToken }

Roles

Role Permissions
user Create posts (verified only), add comments
admin All user permissions + delete any post, publish

Notes

  • Only verified users (email verified) can create posts
  • Access tokens expire in 15 minutes
  • Refresh tokens expire in 7 days and rotate on each use
  • Replay attack protection: using a refresh token twice revokes all sessions

API Endpoints

Auth

Method URL Auth required Description
POST /auth/register No Register and receive verifyToken
POST /auth/verify-email No Verify email with token
POST /auth/login No Login and receive token pair
POST /auth/refresh No Refresh access token
POST /auth/logout No Logout and invalidate refresh token
GET /auth/me Yes Get current user profile

General

Method URL Auth required Description
GET / No API info and available endpoints
GET /stats Yes Aggregated statistics

Posts

Method URL Auth required Description
GET /posts No List posts (pagination + search)
POST /posts Yes (verified) Create a new post
GET /posts/:id No Get a single post with author and comments
PATCH /posts/:id Yes Update a post
DELETE /posts/:id Yes (admin) Delete a post
POST /posts/:id/publish Yes (admin) Publish a post

Query parameters for GET /posts:

Parameter Type Description
page number Page number (default: 1)
limit number Items per page (default: 10)
search string Filter by title or content

Comments

Method URL Auth required Description
GET /posts/:id/comments No List comments on a post
POST /posts/:id/comments Yes Add a comment to a post
DELETE /posts/:id/comments/:commentId Yes (admin) Delete a comment

Users

Method URL Auth required Description
GET /users/:id/posts No Get all posts by a user (with comment counts)

Example Requests & Responses

Register

Request

POST /auth/register
Content-Type: application/json
{
  "name": "John Doe",
  "email": "john@example.com",
  "password": "Password123"
}

Response 201 Created

{
  "message": "Registration successful. Please verify your email.",
  "verifyToken": "a3f8c2..."
}

Verify Email

Request

POST /auth/verify-email
Content-Type: application/json
{
  "token": "a3f8c2..."
}

Response 200 OK

{
  "message": "Email verified successfully"
}

Login

Request

POST /auth/login
Content-Type: application/json
{
  "email": "john@example.com",
  "password": "Password123"
}

Response 200 OK

{
  "accessToken": "eyJhbGci...",
  "refreshToken": "d4e5f6..."
}

Get current user

Request

GET /auth/me
Authorization: Bearer eyJhbGci...

Response 200 OK

{
  "id": 1,
  "name": "John Doe",
  "email": "john@example.com",
  "role": "user",
  "emailVerified": true,
  "createdAt": "2024-01-01T00:00:00.000Z"
}

Get all posts

Request

GET /posts?page=1&limit=10

Response 200 OK

{
  "data": [
    {
      "id": 1,
      "title": "My first blog",
      "content": "Hello world",
      "published": true,
      "authorId": 1,
      "createdAt": "2024-01-01T00:00:00.000Z",
      "updatedAt": "2024-01-01T00:00:00.000Z"
    }
  ],
  "total": 5,
  "page": 1,
  "limit": 10
}

Create a post

Request

POST /posts
Authorization: Bearer eyJhbGci...
Content-Type: application/json
{
  "title": "My first blog",
  "content": "Hello world content here",
  "tags": ["nodejs", "express"]
}

Response 201 Created

{
  "id": 1,
  "title": "My first blog",
  "content": "Hello world content here",
  "published": false,
  "authorId": 1,
  "tags": ["nodejs", "express"],
  "createdAt": "2024-01-01T00:00:00.000Z",
  "updatedAt": "2024-01-01T00:00:00.000Z"
}

Get stats

Request

GET /stats
Authorization: Bearer eyJhbGci...

Response 200 OK

{
  "totalPosts": 5,
  "totalComments": 10,
  "totalUsers": 3,
  "publishedPosts": 4
}

Error Responses

All errors follow a consistent shape:

{
  "error": "Post not found",
  "statusCode": 404
}
Status Meaning
400 Bad request — invalid input
401 Unauthorized — missing or invalid token
403 Forbidden — insufficient permissions or unverified email
404 Resource not found
409 Conflict — duplicate resource
422 Validation error — invalid request body
429 Too many requests — rate limit exceeded
500 Internal server error

Security

Feature Details
Helmet Secure HTTP headers on all responses
Rate limiting 100 req/15min global, 5 req/15min on /auth
CORS Restricted to ALLOWED_ORIGINSin .env
Passwords Hashed with bcrypt (salt rounds: 10)
Refresh tokens Stored hashed in DB, rotate on each use

Project Structure

blog-api/
├── prisma/
│   ├── schema.prisma         # Database schema
│   ├── migrations/           # Migration history
│   └── seed.ts               # Seed script (3 users, 5 posts, 10 comments)
├── src/
│   ├── app.ts                # Express app setup
│   ├── server.ts             # Entry point
│   ├── config.ts             # Zod-validated environment config
│   ├── database/
│   │   └── prisma.ts         # PrismaClient singleton
│   ├── auth/                 # Auth feature module
│   │   ├── auth.controller.ts
│   │   ├── auth.routes.ts
│   │   └── auth.service.ts
│   ├── posts/                # Feature module
│   │   ├── posts.controller.ts
│   │   ├── posts.routes.ts
│   │   ├── posts.service.ts
│   │   └── posts.model.ts
│   ├── comments/             # Feature module
│   │   ├── comments.controller.ts
│   │   ├── comments.routes.ts
│   │   ├── comments.service.ts
│   │   └── comments.model.ts
│   ├── users/
│   ├── stats/
│   ├── middlewares/
│   │   ├── auth.ts           # JWT authentication
│   │   ├── requireRole.ts    # Role-based access control
│   │   ├── requireVerified.ts# Email verification guard
│   │   ├── errorHandler.ts   # Global error handler
│   │   ├── logger.ts         # Request logger
│   │   └── validate.ts       # express-validator runner
│   ├── errors/               # Typed error hierarchy
│   │   ├── AppError.ts
│   │   ├── NotFoundError.ts
│   │   ├── ConflictError.ts
│   │   ├── UnauthorizedError.ts
│   │   ├── ValidationError.ts
│   │   └── index.ts
│   └── types/
│       ├── user.types.ts
│       ├── post.types.ts
│       ├── common.types.ts
│       └── express.d.ts      # Express Request augmentation (req.user)
├── .env.example
├── package.json
└── tsconfig.json

Available Scripts

npm run dev        # Start with hot reload (tsx watch)
npm run build      # Compile TypeScript → dist/
npm start          # Run compiled output

About

RESTful Blog API built with Node.js, Express, TypeScript, Prisma, and PostgreSQL. Features JWT auth, refresh tokens, RBAC, and email verification.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors