Skip to content

Chat — Serializers

Overview

The serializer layer in the chat app defines the API contract between backend and frontend.

It is responsible for:

  • validating request payloads
  • shaping response payloads
  • exposing frontend-friendly message, conversation, participant, and attachment data
  • keeping transport concerns separate from chat workflow logic

The serializer layer should remain thin.

Business logic such as:

  • creating conversations
  • sending messages
  • attaching uploaded files
  • enforcing participant rules
  • marking conversations as read
  • sending notifications
  • websocket broadcasting

belongs in services and consumers, not in serializers.


Serializer structure

The chat app serializer layer is split into focused modules:

  • serializers/attachments.py
  • serializers/conversations.py
  • serializers/messages.py
  • serializers/participants.py

This makes the API surface easier to understand and maintain.


Attachments serializers

MessageAttachmentSerializer

Represents an attachment in API responses.

Purpose

This serializer gives the frontend all metadata needed to display or download an uploaded attachment.

Fields

  • id
  • conversation_id
  • message_id
  • uploaded_by_id
  • filename
  • content_type
  • size_bytes
  • url
  • created_at

Notes

  • message_id may be null if the file has been uploaded but not yet attached to a message
  • url is derived from the file storage backend
  • the serializer supports both:
  • already-linked attachments
  • orphan uploads waiting to be attached

Participants serializers

ParticipantSerializer

Represents a participant in a conversation.

Purpose

This serializer is used to show who is currently part of a conversation and what their read state looks like.

Fields

  • id
  • conversation
  • user_id
  • username
  • display_name
  • role
  • is_active
  • last_read_at
  • joined_at

Notes

The serializer enriches participant data with:

  • username
  • display_name

so the frontend does not need to make extra user lookups for common display cases.


AddParticipantSerializer

Validates requests to add or reactivate a participant.

Fields

  • user_id
  • role

Purpose

This serializer validates the request body for participant addition.

It does not decide whether the user may actually be added.
That belongs in services.


RemoveParticipantSerializer

Validates participant removal payloads if needed.

Fields

  • user_id

Purpose

This serializer is a small transport helper for remove-style operations.

Depending on route design, the user_id may also come from the URL rather than request body.


Messages serializers

MessageSerializer

Represents a chat message in API responses.

This is one of the most important serializers in the app.

Purpose

It provides a frontend-ready message payload for:

  • REST history endpoints
  • edit/delete responses
  • future websocket alignment

Fields

  • id
  • conversation_id
  • sender_id
  • sender_username
  • sender_display_name
  • body
  • data
  • attachments
  • created_at
  • edited_at
  • deleted_at

Notes

This serializer includes nested attachments so the frontend can render complete message rows without separately fetching files.

It also enriches sender information for display.

This is especially important for web and mobile chat UIs.


MessageCreateSerializer

Validates input for message creation.

Fields

  • body
  • data
  • attachment_ids

Validation rules

A message must include at least one of:

  • non-empty body
  • one or more attachments

This allows:

  • normal text messages
  • attachment-only messages
  • mixed text + attachment messages

Notes

This serializer is especially important for a frontend-friendly upload flow:

  1. upload file
  2. get attachment ID
  3. send message with attachment_ids

MessageEditSerializer

Validates input for editing an existing message.

Fields

  • body

Purpose

Used by edit endpoints to validate the new message body.

Authorization is not handled here.
That belongs in services.


MessageListResponseSerializer

Defines the paginated message-history response shape.

Fields

  • items
  • next_before

Purpose

This serializer reflects the chat history pagination contract.

The response shape supports cursor-style pagination using a timestamp boundary.

Notes

next_before is used by the frontend to request older messages.


Conversations serializers

ConversationSerializer

Represents the base conversation model.

Fields

  • id
  • internal_org
  • customer_org
  • title
  • topic_type
  • topic_id
  • created_at
  • closed_at

Purpose

This serializer is suitable for:

  • simple detail responses
  • create responses
  • internal transport use

ConversationListSerializer

Represents a frontend-friendly conversation list item.

Fields

  • id
  • internal_org
  • customer_org
  • title
  • topic_type
  • topic_id
  • created_at
  • closed_at
  • unread_count
  • participant_count
  • last_message

Purpose

This serializer is used for conversation lists where the frontend needs summary data.

Notes

It extends the raw conversation model with read-model fields such as:

  • unread count
  • participant count
  • last message preview

This makes it much more useful for mobile and web chat sidebars or inbox views.


ConversationCreateSerializer

Validates conversation creation payloads.

Fields

  • customer_org_id
  • title
  • topic_type
  • topic_id

Purpose

Used when creating a new conversation from the internal side.

Notes

This serializer validates payload shape only.

It does not decide:

  • whether the customer org is linked
  • whether the actor may create the conversation

That belongs in services.


Serializer responsibilities by layer

Serializer layer

Responsible for:

  • validating payload shape
  • normalizing request data
  • exposing stable frontend-facing response structures
  • adding lightweight display-oriented enrichment

Service layer

Responsible for:

  • creating conversations
  • sending messages
  • attaching files
  • checking permissions
  • handling participant membership
  • computing unread counts
  • marking read state

View layer

Responsible for:

  • receiving HTTP requests
  • invoking serializers
  • calling services
  • returning HTTP responses

Consumer layer

Responsible for:

  • websocket message handling
  • websocket event broadcasting
  • real-time delivery

Read-model enrichment

The chat serializers include frontend-focused convenience fields.

These are not business rules.
They are response-shaping features.

Message enrichment

MessageSerializer includes:

  • sender username
  • sender display name
  • nested attachments

This makes the message payload much easier for frontend rendering.

Participant enrichment

ParticipantSerializer includes:

  • display_name
  • username

This avoids extra lookups for participant chips or presence lists.

Conversation enrichment

ConversationListSerializer includes:

  • unread_count
  • participant_count
  • last_message

This gives the frontend the data needed to render a proper conversation list.


Data flow

flowchart TD
    A[HTTP request] --> B[Serializer validation]
    B --> C[View]
    C --> D[Service layer]
    D --> E[Models]
    E --> F[Serializer output]
    F --> G[HTTP response]

Message creation flow

flowchart TD
    A[Client sends message payload] --> B[MessageCreateSerializer]
    B --> C[Validate body or attachments]
    C --> D[View calls message service]
    D --> E[Message persisted]
    E --> F[Response shaped with MessageSerializer]

Attachment upload flow

flowchart TD
    A[Client uploads file] --> B[Upload view]
    B --> C[Attachment service]
    C --> D[MessageAttachment created]
    D --> E[MessageAttachmentSerializer]
    E --> F[Attachment metadata returned]

Conversation list flow

flowchart TD
    A[List conversations request] --> B[Conversation service]
    B --> C[Attach unread and last_message data]
    C --> D[ConversationListSerializer]
    D --> E[Frontend-ready conversation list response]

Frontend readiness benefits

The serializer split makes the chat backend much easier to integrate into web and mobile clients.

It gives the frontend: - clear message payloads - nested attachment data - participant display metadata - conversation summary data - stable pagination response shapes

This reduces the need for frontend-side stitching or extra requests.


What serializers do not do

These serializers do not: - check whether the requester is a participant - decide who can edit/delete messages - attach uploaded files to messages - calculate unread counts themselves - create notifications - broadcast websocket events - enforce org link rules

Those responsibilities belong to: - services - views - consumers


Design principles

Thin serializers

Serializers should validate and shape data, not run workflow logic.

Frontend-friendly payloads

Responses should be ready to render in chat UIs with minimal extra processing.

Stable message shape

The same message structure should ideally be shared across: - REST history - REST action responses - websocket events

Focused modules

Splitting serializers by domain keeps the chat API easier to evolve.


Best practices

  • keep request validation separate from service decisions
  • use read serializers for frontend-facing payloads
  • use dedicated write serializers for create/edit actions
  • include nested attachments where message rendering needs them
  • keep serializer-enriched fields lightweight
  • avoid embedding permission logic in serializers

Future extensions

Possible future serializer improvements include: - separate detail vs list serializers for conversations - message reaction serializers - read receipt serializers - richer participant presence serializers - system message serializers - message type-specific serializers for structured content - websocket event serializers matching REST serializers exactly


Summary

The chat serializer layer provides the API-facing structure for: - attachment metadata - participant data - message payloads - paginated history responses - conversation list items - conversation creation input

It ensures the chat API is: - structured - frontend-friendly - easy to evolve - clearly separated from business workflow logic