A Generic Open Data Backend
A production-ready, open-source REST API that can store and serve ANY type of data using categories and flexible JSON structures.
π Live Demo: https://data-hub-api.vercel.app
β οΈ PENTING / IMPORTANT: Live demo di Vercel hanya menampilkan halaman dokumentasi frontend saja. API endpoint (CRUD, Auth, dll) TIDAK bisa digunakan di Vercel karena project ini menggunakan SQLite (file-based database) yang tidak kompatibel dengan Vercel serverless (read-only filesystem).Untuk testing API secara penuh, silakan clone repository dan jalankan secara lokal. Lihat bagian Getting Started dan Local Testing Guide.
Data Hub API is a generic open data backend designed to store and serve ANY type of data using categories and flexible JSON structures. The API is designed to be consumed by:
- Web applications
- Mobile applications
- Dashboards
- External services
This project provides:
- RESTful API with Next.js App Router
- JWT Authentication with access/refresh tokens
- Role-based access control (admin/user roles)
- Flexible JSON data storage
- Developer documentation frontend
- JWT-based authentication with access and refresh tokens
- Role-based access control (admin/user)
- bcrypt password hashing
- Public read access for all data
- Admin-only write operations
- Flexible JSON-based data storage
- Categorization system
- Data type classification
- Full CRUD operations for admin users
- RESTful API design
- Consistent response format
- JSON content storage
- Query parameter filtering
- Comprehensive error handling
- Interactive API documentation
- Example requests (curl and fetch)
- Clear authentication flow
- Developer-friendly guides
- Next.js 16 - React framework with App Router
- TypeScript 5 - Type-safe development
- Prisma ORM - Database toolkit
- SQLite - Database (production-ready for PostgreSQL migration)
- jose - JWT token generation/verification
- bcrypt - Password hashing
- Next.js 16 with App Router
- TypeScript 5
- Tailwind CSS 4
- shadcn/ui components
- Lucide icons
User accounts with authentication support
id- Primary keyemail- Unique email addresspasswordHash- Bcrypt hashed passwordrole- User role (admin/user)isActive- Account statuscreatedAt- Creation timestampupdatedAt- Update timestamp
Categories for organizing data
id- Primary keyname- Category namedescription- Category descriptioncreatedAt- Creation timestampupdatedAt- Update timestamp
Data type classification
id- Primary keyname- Type namedescription- Type descriptioncreatedAt- Creation timestampupdatedAt- Update timestamp
Flexible data entries with JSON content
id- Primary keycategoryId- Foreign key to categoriestypeId- Foreign key to dataTypestitle- Entry titlecontent- JSON content (stored as TEXT)source- Data sourcecreatedAt- Creation timestampupdatedAt- Update timestamp
Authenticate with username and password.
Request Body:
{
"username": "JabesNelma",
"password": "your-password"
}Response:
{
"success": true,
"data": {
"user": {
"id": "user-id",
"username": "JabesNelma",
"role": "admin"
},
"accessToken": "jwt-access-token",
"refreshToken": "jwt-refresh-token"
},
"message": "Login successful"
}Refresh access token using refresh token.
Request Body:
{
"refreshToken": "jwt-refresh-token"
}Response:
{
"success": true,
"data": {
"accessToken": "new-jwt-access-token"
},
"message": "Token refreshed successfully"
}Logout current session.
Response:
{
"success": true,
"message": "Logout successful"
}Get all data entries (public read access).
Query Parameters:
categoryId(optional) - Filter by categorytypeId(optional) - Filter by type
Response:
{
"success": true,
"data": [
{
"id": "entry-id",
"categoryId": "category-id",
"typeId": "type-id",
"title": "Entry Title",
"content": { "key": "value" },
"source": "source",
"createdAt": "2025-01-01T00:00:00Z",
"updatedAt": "2025-01-01T00:00:00Z",
"category": { "id": "...", "name": "..." },
"type": { "id": "...", "name": "..." }
}
],
"message": "Data entries retrieved successfully"
}Get data entry by ID (public read access).
Response:
{
"success": true,
"data": {
"id": "entry-id",
"categoryId": "category-id",
"typeId": "type-id",
"title": "Entry Title",
"content": { "key": "value" },
"source": "source",
"createdAt": "2025-01-01T00:00:00Z",
"updatedAt": "2025-01-01T00:00:00Z",
"category": { "id": "...", "name": "..." },
"type": { "id": "...", "name": "..." }
},
"message": "Data entry retrieved successfully"
}Create new data entry.
Request Headers:
Authorization: Bearer {access-token}
Request Body:
{
"categoryId": "category-id",
"typeId": "type-id",
"title": "Entry Title",
"content": { "key": "value", "nested": { "data": "here" } },
"source": "optional-source"
}Response:
{
"success": true,
"data": {
"id": "entry-id",
"categoryId": "category-id",
"typeId": "type-id",
"title": "Entry Title",
"content": { "key": "value" },
"source": "optional-source",
"createdAt": "2025-01-01T00:00:00Z",
"updatedAt": "2025-01-01T00:00:00Z",
"category": { "id": "...", "name": "..." },
"type": { "id": "...", "name": "..." }
},
"message": "Data entry created successfully"
}Update data entry.
Request Headers:
Authorization: Bearer {access-token}
Request Body:
{
"categoryId": "category-id",
"typeId": "type-id",
"title": "Updated Title",
"content": { "key": "updated-value" },
"source": "updated-source"
}Response:
{
"success": true,
"data": {
"id": "entry-id",
...
},
"message": "Data entry updated successfully"
}Delete data entry.
Request Headers:
Authorization: Bearer {access-token}
Response:
{
"success": true,
"message": "Data entry deleted successfully"
}Get all categories (public read access).
Response:
{
"success": true,
"data": [
{
"id": "category-id",
"name": "General",
"description": "General data entries",
"createdAt": "2025-01-01T00:00:00Z",
"updatedAt": "2025-01-01T00:00:00Z",
"_count": { "dataEntries": 10 }
}
],
"message": "Categories retrieved successfully"
}Get all data types (public read access).
Response:
{
"success": true,
"data": [
{
"id": "type-id",
"name": "JSON",
"description": "JSON formatted data",
"createdAt": "2025-01-01T00:00:00Z",
"updatedAt": "2025-01-01T00:00:00Z",
"_count": { "dataEntries": 15 }
}
],
"message": "Data types retrieved successfully"
}Create initial admin user and default data.
Request Body:
{
"username": "JabesNelma",
"password": "secure-password",
"seedKey": "admin-setup-key"
}Response:
{
"success": true,
"data": {
"user": { "id": "...", "username": "JabesNelma", "role": "admin" },
"categories": [...],
"types": [...]
},
"message": "Admin user and default data seeded successfully"
}- Node.js 18+
- Bun (recommended) or npm/yarn
-
Clone the repository
git clone https://github.com/your-username/data-hub-api.git cd data-hub-api -
Install dependencies
bun install
-
Configure environment variables
cp .env.example .env
Edit
.envand update the values:DATABASE_URL="file:./db/custom.db" JWT_SECRET="your-secret-key-min-32-chars" JWT_REFRESH_SECRET="your-refresh-secret-key-min-32-chars" SEED_KEY="admin-setup-key" NODE_ENV="development"
IMPORTANT: Generate strong secrets for production!
-
Set up the database
bun run db:push
-
Create admin user
curl -X POST http://localhost:3000/api/admin/seed \ -H "Content-Type: application/json" \ -d '{ "username": "JabesNelma", "password": "your-secure-password", "seedKey": "admin-setup-key" }'
-
Start the development server
bun run dev
-
Access the application
- API: http://localhost:3000/api
- Documentation: http://localhost:3000
curl -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"username": "JabesNelma",
"password": "your-password"
}'# Get all data
curl http://localhost:3000/api/data
# Get data with filters
curl "http://localhost:3000/api/data?categoryId=category-id&typeId=type-id"
# Get specific entry
curl http://localhost:3000/api/data/entry-idcurl -X POST http://localhost:3000/api/data \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-d '{
"categoryId": "category-id",
"typeId": "type-id",
"title": "My Data Entry",
"content": {
"key": "value",
"nested": {
"data": "here"
}
},
"source": "my-app"
}'// Get all data (public)
const response = await fetch('http://localhost:3000/api/data');
const { success, data, message } = await response.json();
if (success) {
console.log('Data entries:', data);
}
// Create data entry (requires admin token)
const createResponse = await fetch('http://localhost:3000/api/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_ACCESS_TOKEN'
},
body: JSON.stringify({
categoryId: 'category-id',
typeId: 'type-id',
title: 'New Entry',
content: { key: 'value' },
source: 'my-app'
})
});
const result = await createResponse.json();data-hub-api/
βββ prisma/
β βββ schema.prisma # Database schema
β βββ migrations/ # Database migrations
βββ src/
β βββ app/
β β βββ api/
β β β βββ auth/ # Authentication routes
β β β β βββ login/
β β β β βββ refresh/
β β β β βββ logout/
β β β βββ data/ # Data management routes
β β β β βββ [id]/
β β β βββ categories/
β β β βββ types/
β β β βββ admin/
β β β βββ seed/
β β βββ page.tsx # Documentation landing page
β β βββ layout.tsx
β βββ components/
β β βββ ui/ # shadcn/ui components
β βββ lib/
β βββ db.ts # Prisma client
β βββ auth.ts # JWT utilities
β βββ api-auth.ts # API authentication middleware
βββ db/
β βββ custom.db # SQLite database file
βββ .env.example # Environment variables template
βββ LICENSE # MIT License
βββ README.md # This file
βββ package.json
- Lifetime: 15 minutes
- Usage: API requests
- Header:
Authorization: Bearer {token}
- Lifetime: 7 days
- Usage: Get new access tokens
- More secure, long-lived
- Send POST request to
/api/auth/loginwith email and password - Receive access token and refresh token in response
- Include access token in Authorization header:
Bearer {token} - When access token expires, use refresh token to get a new one
- Include new access token in subsequent requests
- Public Access: GET requests to
/api/data,/api/categories,/api/types - Admin Required: POST, PUT, DELETE requests to
/api/data/*
- Push your code to GitHub
- Connect repository to Vercel
- Configure environment variables in Vercel dashboard:
DATABASE_URLJWT_SECRETJWT_REFRESH_SECRETSEED_KEYNODE_ENV(set toproduction)
- Deploy
This project can be deployed to any platform that supports Next.js:
- Vercel (recommended)
- Netlify
- Railway
- Render
- Self-hosted with Docker
Make sure to set these in your production environment:
DATABASE_URL="your-database-url"
JWT_SECRET="strong-secret-at-least-32-characters"
JWT_REFRESH_SECRET="another-strong-secret-at-least-32-characters"
SEED_KEY="unique-seed-key-for-initial-setup"
NODE_ENV="production"IMPORTANT: Use strong, randomly generated secrets in production!
bun run lint# Push schema changes to database
bun run db:push
# Open Prisma Studio (database GUI)
bun run db:studio
# Generate Prisma Client
bun run db:generate# Lint code
bun run lint
# Type check
bun run type-checkKenapa harus lokal? Project ini menggunakan SQLite (file-based database). Vercel menggunakan serverless functions dengan filesystem read-only, sehingga database tidak bisa dibuat/ditulis. Untuk testing API, wajib jalankan di lokal.
git clone https://github.com/JabesNelma/data-hub-api.git
cd data-hub-api
npm install
cp .env.example .env
npm run db:push
npm run devcurl -s -X POST http://localhost:3000/api/admin/seed \
-H "Content-Type: application/json" \
-d '{
"username": "JabesNelma",
"password": "SecurePassword123!",
"seedKey": "admin-setup-key"
}'curl -s -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"username": "JabesNelma",
"password": "SecurePassword123!"
}'Simpan accessToken dari response untuk digunakan di request selanjutnya.
# GET categories (public)
curl -s http://localhost:3000/api/categories
# GET types (public)
curl -s http://localhost:3000/api/types
# GET all data (public)
curl -s http://localhost:3000/api/data
# POST create data (admin only)
curl -s -X POST http://localhost:3000/api/data \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-d '{
"categoryId": "CATEGORY_ID",
"typeId": "TYPE_ID",
"title": "Data Penduduk Jakarta",
"content": {
"provinsi": "DKI Jakarta",
"populasi": 10500000,
"tahun": 2026
},
"source": "BPS Indonesia"
}'
# GET data by ID (public)
curl -s http://localhost:3000/api/data/DATA_ID
# PUT update data (admin only)
curl -s -X PUT http://localhost:3000/api/data/DATA_ID \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-d '{
"title": "Updated Title",
"content": { "key": "updated-value" }
}'
# DELETE data (admin only)
curl -s -X DELETE http://localhost:3000/api/data/DATA_ID \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# Refresh token
curl -s -X POST http://localhost:3000/api/auth/refresh \
-H "Content-Type: application/json" \
-d '{ "refreshToken": "YOUR_REFRESH_TOKEN" }'
# Logout
curl -s -X POST http://localhost:3000/api/auth/logout \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"Semua endpoint telah diuji dan berjalan dengan baik secara lokal:
| # | Endpoint | Method | Status | Keterangan |
|---|---|---|---|---|
| 1 | /api/admin/seed |
POST | β Pass | Admin user + categories + types berhasil dibuat |
| 2 | /api/auth/login |
POST | β Pass | JWT access token & refresh token didapat |
| 3 | /api/categories |
GET | β Pass | 3 categories: General, Documentation, API |
| 4 | /api/types |
GET | β Pass | 3 types: JSON, Text, Structured |
| 5 | /api/data |
GET | β Pass | List semua data entries |
| 6 | /api/data |
POST | β Pass | Data entry baru berhasil dibuat |
| 7 | /api/data/:id |
GET | β Pass | Get data by ID berhasil |
| 8 | /api/data/:id |
PUT | β Pass | Data berhasil diupdate |
| 9 | /api/data/:id |
DELETE | β Pass | Data berhasil dihapus |
| 10 | /api/auth/refresh |
POST | β Pass | Access token baru didapat |
| 11 | /api/auth/logout |
POST | β Pass | Logout berhasil |
| Platform | Frontend Docs | API (SQLite) | Keterangan |
|---|---|---|---|
| Localhost | β | β | Full functionality β recommended untuk testing |
| Vercel | β | β | Hanya frontend docs, API error karena SQLite read-only |
| Railway | β | β | Support persistent disk β bisa pakai SQLite |
| VPS / Docker | β | β | Full control β recommended untuk production |
Tips: Jika ingin deploy API ke production, pertimbangkan migrasi database ke PostgreSQL atau MySQL dan deploy ke platform yang support persistent storage seperti Railway, Render, atau VPS sendiri.
This project is licensed under the MIT License - see the LICENSE file for details.
Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
For issues, questions, or contributions:
- Open an issue on GitHub
- Check the documentation at http://localhost:3000
- Review the API endpoints above
Made with β€οΈ for the developer community
π Live Demo (Frontend Only): https://data-hub-api.vercel.app
π¦ Repository: https://github.com/JabesNelma/data-hub-api
Created by Jabes Nelma
Junior Full Stack Developer β’ MIT License β’ Open Source