Mark Notification as Read API
Overviewβ
The Mark Notification as Read API allows authenticated users to mark a specific notification as read. This endpoint updates the notification status in the database and helps manage user notification states.
Endpoint Detailsβ
- URL:
/api/notifications/[id]/read
- Method:
POST
- Authentication: Required (NextAuth.js session)
- Content-Type:
application/json
URL Parametersβ
Parameter | Type | Required | Description |
---|---|---|---|
id | string | Yes | The unique identifier of the notification to mark as read |
Authenticationβ
This endpoint requires user authentication via NextAuth.js session:
import { getSession } from 'next-auth/react'
const session = await getSession()
if (!session) {
// Handle unauthenticated state
}
Request Bodyβ
This endpoint does not require a request body. The notification ID is provided in the URL path.
Response Formatβ
Success Response (200)β
{
"success": true,
"message": "Notification marked as read"
}
Error Responsesβ
Unauthorized (401)β
{
"error": "User not authenticated"
}
Bad Request (400)β
{
"error": "Notification ID is required"
}
Forbidden (403)β
{
"error": "Unauthorized access to notification"
}
Not Found (404)β
{
"error": "Notification not found"
}
Internal Server Error (500)β
{
"error": "Failed to mark notification as read. Please try again later."
}
Implementation Examplesβ
React Component with Individual Notificationβ
import { useState } from 'react'
import { useSession } from 'next-auth/react'
interface Notification {
id: string
title: string
message: string
isRead: boolean
createdAt: string
type: 'info' | 'warning' | 'success' | 'error'
}
function NotificationItem({ notification, onMarkAsRead }: {
notification: Notification
onMarkAsRead: (id: string) => void
}) {
const { data: session } = useSession()
const [loading, setLoading] = useState(false)
const handleMarkAsRead = async () => {
if (!session || notification.isRead) return
setLoading(true)
try {
const response = await fetch(`/api/notifications/${notification.id}/read`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Failed to mark as read')
}
onMarkAsRead(notification.id)
} catch (error) {
console.error('Error marking notification as read:', error)
} finally {
setLoading(false)
}
}
return (
<div className={`p-4 border rounded-lg ${
notification.isRead ? 'bg-gray-50' : 'bg-white border-blue-200'
}`}>
<div className="flex items-start justify-between">
<div className="flex-1">
<h4 className={`font-semibold ${
notification.isRead ? 'text-gray-600' : 'text-gray-900'
}`}>
{notification.title}
</h4>
<p className={`mt-1 text-sm ${
notification.isRead ? 'text-gray-500' : 'text-gray-700'
}`}>
{notification.message}
</p>
<span className="text-xs text-gray-400 mt-2 block">
{new Date(notification.createdAt).toLocaleDateString()}
</span>
</div>
{!notification.isRead && (
<button
onClick={handleMarkAsRead}
disabled={loading}
className="ml-4 px-3 py-1 text-sm bg-blue-500 text-white rounded hover:bg-blue-600 disabled:opacity-50"
>
{loading ? 'Marking...' : 'Mark as Read'}
</button>
)}
</div>
{!notification.isRead && (
<div className="w-2 h-2 bg-blue-500 rounded-full absolute top-4 right-4"></div>
)}
</div>
)
}
React Hook for Notification Managementβ
import { useState, useCallback } from 'react'
import { useSession } from 'next-auth/react'
export function useNotifications() {
const { data: session } = useSession()
const [notifications, setNotifications] = useState<Notification[]>([])
const [loading, setLoading] = useState(false)
const markAsRead = useCallback(async (notificationId: string) => {
if (!session) return
try {
const response = await fetch(`/api/notifications/${notificationId}/read`, {
method: 'POST',
})
if (!response.ok) {
throw new Error('Failed to mark notification as read')
}
// Update local state
setNotifications(prev =>
prev.map(notification =>
notification.id === notificationId
? { ...notification, isRead: true }
: notification
)
)
return true
} catch (error) {
console.error('Error marking notification as read:', error)
return false
}
}, [session])
const markAllAsRead = useCallback(async () => {
if (!session) return
setLoading(true)
try {
const response = await fetch('/api/notifications/read-all', {
method: 'POST',
})
if (!response.ok) {
throw new Error('Failed to mark all notifications as read')
}
// Update local state
setNotifications(prev =>
prev.map(notification => ({ ...notification, isRead: true }))
)
} catch (error) {
console.error('Error marking all notifications as read:', error)
} finally {
setLoading(false)
}
}, [session])
return {
notifications,
setNotifications,
markAsRead,
markAllAsRead,
loading
}
}
Server Action (Next.js 13+)β
'use server'
import { auth } from '@/auth'
import { redirect } from 'next/navigation'
import { revalidatePath } from 'next/cache'
export async function markNotificationAsReadAction(notificationId: string) {
const session = await auth()
if (!session) {
redirect('/auth/signin')
}
try {
const response = await fetch(`${process.env.NEXTAUTH_URL}/api/notifications/${notificationId}/read`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cookie': request.headers.get('cookie') || '',
},
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Failed to mark notification as read')
}
// Revalidate the notifications page
revalidatePath('/notifications')
} catch (error) {
console.error('Server action error:', error)
throw error
}
}
cURL Exampleβ
curl -X POST https://ring.ck.ua/api/notifications/notification123/read \
-H "Content-Type: application/json" \
-H "Cookie: next-auth.session-token=your-session-token"
Business Logicβ
The endpoint performs the following operations:
- Authentication Check: Verifies user session and extracts user ID
- Parameter Validation: Ensures notification ID is provided in URL
- Authorization Check: Verifies user has access to the notification
- Database Update: Marks the notification as read in Firestore
- Response: Returns success confirmation
Database Schemaβ
Notification Document Structureβ
{
"id": "notification123",
"userId": "user456",
"title": "New message received",
"message": "You have a new message from John Doe",
"type": "info",
"isRead": false,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z",
"metadata": {
"sourceId": "message789",
"sourceType": "message",
"actionUrl": "/messages/789"
}
}
After Marking as Readβ
{
"id": "notification123",
"userId": "user456",
"title": "New message received",
"message": "You have a new message from John Doe",
"type": "info",
"isRead": true,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T14:22:00Z",
"readAt": "2024-01-15T14:22:00Z",
"metadata": {
"sourceId": "message789",
"sourceType": "message",
"actionUrl": "/messages/789"
}
}
Security Considerationsβ
- Authentication Required: Only authenticated users can mark notifications as read
- Authorization Check: Users can only mark their own notifications as read
- Input Validation: Notification ID is validated for format and existence
- Error Handling: Sensitive information is not exposed in error messages
Performance Notesβ
- Single Database Query: Efficiently updates one notification record
- No Caching: Real-time updates ensure immediate consistency
- Optimistic Updates: Frontend can update UI before server confirmation
Rate Limitingβ
- User-based: 30 requests per minute per authenticated user
- Global: 1000 requests per minute across all users
Error Handlingβ
The API implements comprehensive error handling:
- Missing notification ID returns 400 status
- Authentication errors return 401 status
- Authorization errors return 403 status
- Missing notifications return 404 status
- Server errors return 500 status with user-friendly messages
Integration with Real-time Updatesβ
// WebSocket integration for real-time notification updates
import { useEffect } from 'react'
import { useWebSocket } from '@/hooks/useWebSocket'
function NotificationSystem() {
const { socket } = useWebSocket()
const { notifications, setNotifications, markAsRead } = useNotifications()
useEffect(() => {
if (!socket) return
// Listen for real-time notification updates
socket.on('notification:read', (data: { notificationId: string }) => {
setNotifications(prev =>
prev.map(notification =>
notification.id === data.notificationId
? { ...notification, isRead: true, readAt: new Date().toISOString() }
: notification
)
)
})
return () => {
socket.off('notification:read')
}
}, [socket, setNotifications])
return (
<div className="notification-system">
{/* Notification components */}
</div>
)
}
Testingβ
Unit Test Exampleβ
import { POST } from '@/app/api/notifications/[id]/read/route'
import { NextRequest } from 'next/server'
// Mock authentication and services
jest.mock('@/auth')
jest.mock('@/services/notifications/notification-service')
describe('/api/notifications/[id]/read', () => {
it('should mark notification as read successfully', async () => {
// Mock authenticated session
(getServerAuthSession as jest.Mock).mockResolvedValue({
user: { id: 'user123', role: 'member' }
})
const request = new NextRequest('http://localhost:3000/api/notifications/notif123/read', {
method: 'POST'
})
const response = await POST(request, { params: { id: 'notif123' } })
const data = await response.json()
expect(response.status).toBe(200)
expect(data.success).toBe(true)
expect(data.message).toBe('Notification marked as read')
})
it('should return 404 for non-existent notification', async () => {
(markNotificationAsRead as jest.Mock).mockRejectedValue(
new Error('Notification not found')
)
const request = new NextRequest('http://localhost:3000/api/notifications/invalid/read', {
method: 'POST'
})
const response = await POST(request, { params: { id: 'invalid' } })
const data = await response.json()
expect(response.status).toBe(404)
expect(data.error).toBe('Notification not found')
})
})
Related Endpointsβ
GET /api/notifications
- List user notificationsPOST /api/notifications/read-all
- Mark all notifications as readGET /api/notifications/preferences
- Get notification preferencesPUT /api/notifications/preferences
- Update notification preferences