This document provides comprehensive documentation for all API endpoints in the Developer Portfolio Dashboard. This is an open-source project available under the MIT License.
The API supports both public endpoints for the portfolio frontend and protected admin endpoints for content management through the admin dashboard interface.
Key Features:
- Complete CRUD operations for projects, experiences, and contact messages
- Secure admin authentication with Clerk integration
- File upload system with validation and cleanup
- Rate limiting and comprehensive security headers
- Database health monitoring and auto-repair
- Responsive admin dashboard with real-time updates
- Public portfolio endpoints for displaying content
- Framework: Next.js 15.4.6 with App Router
- Runtime: Node.js 18+
- Database: NeonDB (PostgreSQL) with @neondatabase/serverless 0.9.0
- Authentication: Clerk 6.31.1
- Styling: TailwindCSS 3.4.3 with Headless UI 2.2.7
- Animations: Framer Motion 10.18.0
http://localhost:3000/api (development)
https://your-domain.com/api (production)
Most admin endpoints require authentication via Clerk. Include the Clerk session token in your requests.
The API endpoints are integrated with a comprehensive admin dashboard interface that provides:
- Project Management: Full CRUD interface at
/admin/projectswith image upload, editing, and deletion - Real-time Updates: Automatic refresh and notifications for all operations
- Responsive Design: Mobile-friendly interface with glassmorphism design
- File Management: Drag-and-drop image upload with validation and preview
- Error Handling: User-friendly error messages and success notifications
/admin/dashboard- Main dashboard overview/admin/projects- Project management interface (✅ Implemented)/admin/experience- Experience management interface (🚧 Planned)/admin/messages- Contact message management interface (🚧 Planned)/admin/profile- Admin profile management
All API endpoints are rate-limited to prevent abuse:
- Public API: 100 requests per minute
- Admin API: 200 requests per minute
- Contact Form: 3 submissions per 15 minutes
- File Upload: 10 uploads per minute
Rate limit headers are included in responses:
X-RateLimit-Limit: Request limit per windowX-RateLimit-Remaining: Remaining requests in current windowX-RateLimit-Reset: Time when the rate limit resets
Check database connectivity and health status.
Response:
{
"success": true,
"health": {
"connected": true,
"tablesExist": {
"projects": true,
"experiences": true,
"contacts": true,
"admins": true
},
"indexesExist": true,
"lastChecked": "2025-08-15T10:30:00.000Z"
},
"timestamp": "2025-08-15T10:30:00.000Z"
}Status Codes:
200: Database is healthy503: Database issues detected500: Health check failed
Attempt to auto-repair database issues.
Response:
{
"success": true,
"message": "Database successfully repaired",
"timestamp": "2025-08-15T10:30:00.000Z"
}Status Codes:
200: Repair successful500: Repair failed
Get all projects or featured projects only. Public endpoint - no authentication required.
Query Parameters:
featured(optional): Set totrueto get only featured projects
Response:
{
"success": true,
"data": [
{
"id": 1,
"title": "Project Title",
"description": "Project description",
"techStack": ["React", "TypeScript", "Next.js"],
"githubUrl": "https://github.com/user/repo",
"demoUrl": "https://demo.example.com",
"imageUrl": "/uploads/projects/image.jpg",
"featured": true,
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-01-15T10:30:00.000Z"
}
],
"message": "Retrieved 1 projects"
}Status Codes:
200: Success500: Server error
Create a new project. Requires admin authentication.
Request Body:
{
"title": "New Project",
"description": "Project description with at least 10 characters",
"techStack": ["React", "TypeScript", "Next.js"],
"githubUrl": "https://github.com/user/repo",
"demoUrl": "https://demo.example.com",
"imageUrl": "/uploads/projects/image.jpg",
"featured": false
}Validation Rules:
title: Required, minimum 3 charactersdescription: Required, minimum 10 characterstechStack: Required, at least one technologygithubUrl: Optional, must be valid URL if provideddemoUrl: Optional, must be valid URL if providedimageUrl: Required, cannot be empty stringfeatured: Optional, defaults to false
Response:
{
"success": true,
"data": {
"id": 2,
"title": "New Project",
"description": "Project description with at least 10 characters",
"techStack": ["React", "TypeScript", "Next.js"],
"githubUrl": "https://github.com/user/repo",
"demoUrl": "https://demo.example.com",
"imageUrl": "/uploads/projects/image.jpg",
"featured": false,
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-01-15T10:30:00.000Z"
},
"message": "Project created successfully"
}Status Codes:
201: Project created successfully400: Validation error or missing required fields401: Authentication required500: Server error
Update an existing project. Requires admin authentication.
Parameters:
id: Project ID (number)
Request Body: (partial update supported - only include fields to update)
{
"title": "Updated Project Title",
"description": "Updated description",
"techStack": ["React", "TypeScript", "Node.js"],
"githubUrl": "https://github.com/user/updated-repo",
"demoUrl": "https://updated-demo.example.com",
"imageUrl": "/uploads/projects/new-image.jpg",
"featured": true
}Response:
{
"success": true,
"data": {
"id": 1,
"title": "Updated Project Title",
"description": "Updated description",
"techStack": ["React", "TypeScript", "Node.js"],
"githubUrl": "https://github.com/user/updated-repo",
"demoUrl": "https://updated-demo.example.com",
"imageUrl": "/uploads/projects/new-image.jpg",
"featured": true,
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-01-16T14:20:00.000Z"
},
"message": "Project updated successfully"
}Status Codes:
200: Project updated successfully400: Invalid project ID or validation error401: Authentication required404: Project not found500: Server error
Delete a project and its associated image file. Requires admin authentication.
Parameters:
id: Project ID (number)
Response:
{
"success": true,
"message": "Project \"Project Title\" deleted successfully"
}Status Codes:
200: Project deleted successfully400: Invalid project ID401: Authentication required404: Project not found500: Server error
Note: This endpoint automatically cleans up associated image files from the filesystem. The implementation includes proper error handling for file cleanup operations, logging warnings if file deletion fails but not failing the database deletion.
Get all work experiences, ordered chronologically with current positions first. Public endpoint - no authentication required.
Response:
{
"success": true,
"data": [
{
"id": 1,
"company": "Tech Company",
"position": "Senior Developer",
"startDate": "2023-01-15T00:00:00.000Z",
"endDate": null,
"description": "Job description with detailed responsibilities",
"achievements": ["Achievement 1", "Achievement 2"],
"technologies": ["React", "Node.js", "TypeScript"],
"companyLogo": "/uploads/companies/company.png",
"location": "San Francisco, CA",
"employmentType": "Full-time",
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-01-15T10:30:00.000Z"
}
],
"message": "Retrieved 1 experiences"
}Sorting Logic:
- Current positions (endDate is null) appear first
- Then sorted by end date (most recent first)
- Finally sorted by start date (most recent first)
Status Codes:
200: Success500: Server error
Create a new experience entry. Requires admin authentication.
Request Body:
{
"company": "New Company",
"position": "Software Developer",
"startDate": "2024-01-15",
"endDate": "2024-12-15",
"description": "Detailed job description with at least 10 characters",
"achievements": ["Achievement 1", "Achievement 2"],
"technologies": ["JavaScript", "Python", "React"],
"companyLogo": "/uploads/companies/company.png",
"location": "Remote",
"employmentType": "Contract"
}Validation Rules:
company: Required, minimum 2 charactersposition: Required, minimum 2 charactersstartDate: Required, valid dateendDate: Optional, must be after startDate if provideddescription: Required, minimum 10 charactersachievements: Optional array of stringstechnologies: Optional array of stringscompanyLogo: Optional, valid image URLlocation: Required, minimum 2 charactersemploymentType: Required, must be one of: 'Full-time', 'Part-time', 'Contract', 'Freelance', 'Internship'
Response:
{
"success": true,
"data": {
"id": 2,
"company": "New Company",
"position": "Software Developer",
"startDate": "2024-01-15T00:00:00.000Z",
"endDate": "2024-12-15T00:00:00.000Z",
"description": "Detailed job description with at least 10 characters",
"achievements": ["Achievement 1", "Achievement 2"],
"technologies": ["JavaScript", "Python", "React"],
"companyLogo": "/uploads/companies/company.png",
"location": "Remote",
"employmentType": "Contract",
"createdAt": "2025-01-16T10:30:00.000Z",
"updatedAt": "2025-01-16T10:30:00.000Z"
},
"message": "Experience created successfully"
}Status Codes:
201: Experience created successfully400: Validation error or missing required fields401: Authentication required500: Server error
Update an existing experience entry. Requires admin authentication.
Parameters:
id: Experience ID (number)
Request Body: (partial update supported - only include fields to update)
{
"company": "Updated Company Name",
"position": "Senior Software Developer",
"endDate": "2025-01-15",
"achievements": ["New Achievement", "Updated Achievement"],
"technologies": ["React", "TypeScript", "Node.js", "PostgreSQL"]
}Response:
{
"success": true,
"data": {
"id": 1,
"company": "Updated Company Name",
"position": "Senior Software Developer",
"startDate": "2023-01-15T00:00:00.000Z",
"endDate": "2025-01-15T00:00:00.000Z",
"description": "Original job description",
"achievements": ["New Achievement", "Updated Achievement"],
"technologies": ["React", "TypeScript", "Node.js", "PostgreSQL"],
"companyLogo": "/uploads/companies/company.png",
"location": "San Francisco, CA",
"employmentType": "Full-time",
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-01-16T14:20:00.000Z"
},
"message": "Experience updated successfully"
}Status Codes:
200: Experience updated successfully400: Invalid experience ID or validation error401: Authentication required404: Experience not found500: Server error
Delete an experience entry and its associated company logo file. Requires admin authentication.
Parameters:
id: Experience ID (number)
Response:
{
"success": true,
"message": "Experience \"Senior Developer at Tech Company\" deleted successfully"
}Status Codes:
200: Experience deleted successfully400: Invalid experience ID401: Authentication required404: Experience not found500: Server error
Note: This endpoint automatically cleans up associated company logo files from the filesystem when available.
Submit a contact form message. Public endpoint - no authentication required.
Request Body:
{
"name": "John Doe",
"email": "john@example.com",
"message": "Hello, I'd like to discuss a project with you."
}Validation Rules:
name: Required, minimum 2 charactersemail: Required, valid email format, maximum 254 charactersmessage: Required, minimum 10 characters
Response:
{
"success": true,
"data": {
"id": 1,
"name": "John Doe",
"email": "john@example.com",
"message": "Hello, I'd like to discuss a project with you.",
"read": false,
"createdAt": "2025-01-15T10:30:00.000Z"
},
"message": "Contact message submitted successfully"
}Status Codes:
201: Message submitted successfully400: Validation error or missing required fields500: Server error
Rate Limit: 3 submissions per 15 minutes per IP
Get all contact messages. Requires admin authentication.
Query Parameters:
unread(optional): Set totrueto get only unread messages
Response:
{
"success": true,
"data": [
{
"id": 1,
"name": "John Doe",
"email": "john@example.com",
"message": "Hello, I'd like to discuss a project with you.",
"read": false,
"createdAt": "2025-01-15T10:30:00.000Z"
},
{
"id": 2,
"name": "Jane Smith",
"email": "jane@example.com",
"message": "Interested in your services.",
"read": true,
"createdAt": "2025-01-14T15:20:00.000Z"
}
],
"message": "Retrieved 2 contact messages"
}Status Codes:
200: Success401: Authentication required500: Server error
Get a specific contact message by ID. Requires admin authentication.
Parameters:
id: Contact message ID (number)
Response:
{
"success": true,
"data": {
"id": 1,
"name": "John Doe",
"email": "john@example.com",
"message": "Hello, I'd like to discuss a project with you.",
"read": false,
"createdAt": "2025-01-15T10:30:00.000Z"
},
"message": "Contact message retrieved successfully"
}Status Codes:
200: Message retrieved successfully400: Invalid message ID401: Authentication required404: Message not found500: Server error
Mark a contact message as read or unread. Requires admin authentication.
Parameters:
id: Contact message ID (number)
Request Body:
{
"read": true
}Response:
{
"success": true,
"data": {
"id": 1,
"name": "John Doe",
"email": "john@example.com",
"message": "Hello, I'd like to discuss a project with you.",
"read": true,
"createdAt": "2025-01-15T10:30:00.000Z"
},
"message": "Message marked as read"
}Status Codes:
200: Message updated successfully400: Invalid message ID or read status401: Authentication required404: Message not found500: Server error
Delete a contact message. Requires admin authentication.
Parameters:
id: Contact message ID (number)
Response:
{
"success": true,
"message": "Contact message deleted successfully"
}Status Codes:
200: Message deleted successfully400: Invalid message ID401: Authentication required404: Message not found500: Server error
Upload files (project images or company logos). Requires admin authentication.
Request: Multipart form data
const formData = new FormData()
formData.append('file', file) // File object
formData.append('type', 'project') // 'project' or 'logo'Response:
{
"success": true,
"data": {
"imageUrl": "/uploads/projects/filename.jpg",
"fileName": "secure-filename.jpg",
"originalName": "original-filename.jpg",
"size": 1024000,
"type": "image/jpeg"
},
"message": "File uploaded successfully"
}Validation:
- Project Images: Max 5MB, JPG/PNG/WebP
- Company Logos: Max 2MB, JPG/PNG/WebP/SVG
- Secure filename generation with timestamp and random string
- Path traversal protection
Status Codes:
200: Upload successful400: Validation error (file size, type, missing file)401: Authentication required500: Upload failed
Get storage statistics and file information. Requires admin authentication.
Response:
{
"success": true,
"data": {
"projects": { "count": 5, "totalSize": 2048000 },
"companies": { "count": 2, "totalSize": 512000 },
"total": { "count": 7, "totalSize": 2560000 }
}
}Clean up orphaned files (files not referenced in database). Requires admin authentication.
Response:
{
"success": true,
"data": {
"cleanup": {
"projects": { "deletedFiles": 2, "errors": [] },
"companies": { "deletedFiles": 1, "errors": [] }
},
"summary": {
"totalDeleted": 3,
"totalErrors": 0,
"errors": []
}
}
}Delete a specific image file. Requires admin authentication.
Request:
{
"imageUrl": "/uploads/projects/filename.jpg"
}Response:
{
"success": true,
"message": "File deleted successfully"
}Get current admin user profile. Requires authentication.
Response:
{
"success": true,
"data": {
"id": 1,
"clerkId": "user_abc123",
"email": "admin@example.com",
"name": "Admin User",
"role": "admin",
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-01-15T10:30:00.000Z"
}
}All endpoints return consistent error responses:
{
"success": false,
"error": "Error message",
"code": "ERROR_CODE",
"details": "Additional error details"
}UNAUTHORIZED: Authentication requiredFORBIDDEN: Insufficient permissionsNOT_FOUND: Resource not foundVALIDATION_ERROR: Invalid request dataRATE_LIMIT_EXCEEDED: Too many requestsDATABASE_ERROR: Database operation failedUPLOAD_ERROR: File upload failed
200: Success201: Created400: Bad Request401: Unauthorized403: Forbidden404: Not Found429: Too Many Requests500: Internal Server Error503: Service Unavailable
The API uses service classes for database operations:
getAllProjects(): Get all projectsgetFeaturedProjects(): Get featured projectsgetProjectById(id): Get project by IDcreateProject(data): Create new projectupdateProject(id, data): Update projectdeleteProject(id): Delete project
getAllExperiences(): Get all experiences with chronological sortinggetExperienceById(id): Get experience by IDcreateExperience(data): Create new experience with validationupdateExperience(id, data): Update experience with partial data supportdeleteExperience(id): Delete experience and cleanup associated files
getAllMessages(): Get all contact messagesgetUnreadMessages(): Get unread messagescreateMessage(data): Create new messagemarkAsRead(id): Mark message as readdeleteMessage(id): Delete message
getAdminByClerkId(clerkId): Get admin by Clerk IDupsertAdmin(data): Create or update admindeleteAdmin(clerkId): Delete admin
- Authentication: All admin endpoints require valid Clerk session
- Rate Limiting: Prevents abuse and DoS attacks
- Input Validation: All inputs are validated and sanitized
- SQL Injection Prevention: Parameterized queries used throughout
- File Upload Security: File type and size restrictions
- CORS: Configured for production domains only
- HTTPS: Required in production
- Detailed error messages
- Debug logging enabled
- Relaxed CORS policy
- Local file uploads
- Generic error messages
- Minimal logging
- Strict CORS policy
- Cloud file storage recommended
- Rate limiting enforced
- HTTPS required
Use tools like Postman, curl, or automated testing frameworks to test the API:
# Test database health
curl http://localhost:3000/api/health/db
# Get all projects
curl http://localhost:3000/api/projects
# Get featured projects only
curl http://localhost:3000/api/projects?featured=true
# Submit contact form
curl -X POST http://localhost:3000/api/contact \
-H "Content-Type: application/json" \
-d '{"name":"Test","email":"test@example.com","message":"Test message"}'For API-related issues:
- Check the database health endpoint
- Verify authentication tokens
- Check rate limiting headers
- Review error messages and codes
- Consult the application logs