Skip to main content

Overview

The Message model represents individual messages sent within chat rooms. Each message contains metadata (sender, timestamp, chat room) and polymorphic content that can be either text or image-based.

Model Purpose

Messages are the core communication unit in RealtimeChat. They:
  • Capture user-generated content (text or images)
  • Track message metadata (sender, timestamp, location)
  • Support real-time delivery through GraphQL subscriptions
  • Enable full-text search and message history

Properties

Id
integer
required
Unique identifier for the message.
SentAt
datetime
required
UTC timestamp indicating when the message was sent. Used for message ordering and display.
SenderId
string
required
ID of the user who sent the message. References the ApplicationUser.Id field.
ChatRoomId
integer
required
ID of the chat room where this message was posted. Defines the message’s conversation context.
Content
IMessageContent
required
Polymorphic message content. Can be either TextMessageContent or ImageMessageContent.

Message Content Types

Messages support two types of content through a union type pattern:

TextMessageContent

Plain text messages containing user-written content.
public record TextMessageContent(string Text) : MessageContent;
Example:
{
  "text": "Hello, how are you doing today?"
}

ImageMessageContent

Image-based messages with an optional caption.
public record ImageMessageContent(string Url, string? Caption) : MessageContent;
Example:
{
  "url": "https://cdn.example.com/images/photo-12345.jpg",
  "caption": "Beautiful sunset at the beach"
}
The content type is determined at runtime using GraphQL union types. Clients must use inline fragments to query specific content types.

Relationships

Sender (User)

Each message belongs to a single user identified by SenderId. This references the ApplicationUser model and enables:
  • User attribution for messages
  • Message history per user
  • Authorization checks

Chat Room

Messages are always associated with a specific chat room through ChatRoomId. This relationship:
  • Organizes messages into conversations
  • Defines subscription scope
  • Enables room-based message queries

GraphQL Type Definition

type Message {
  id: Int!
  sentAt: DateTime!
  senderId: String!
  chatRoomId: Int!
  content: MessageContent!
}

union MessageContent = TextMessageContent | ImageMessageContent

type TextMessageContent {
  text: String!
}

type ImageMessageContent {
  url: String!
  caption: String
}
The GraphQL schema uses a union type for MessageContent, allowing clients to handle different message types appropriately.

Example JSON Representations

Text Message

{
  "id": 42,
  "sentAt": "2026-03-05T10:23:45Z",
  "senderId": "user-abc-123",
  "chatRoomId": 5,
  "content": {
    "text": "Has anyone seen the latest release notes?"
  }
}

Image Message with Caption

{
  "id": 43,
  "sentAt": "2026-03-05T10:25:12Z",
  "senderId": "user-xyz-789",
  "chatRoomId": 5,
  "content": {
    "url": "https://storage.example.com/screenshots/feature-demo.png",
    "caption": "New dashboard design mockup"
  }
}

Image Message without Caption

{
  "id": 44,
  "sentAt": "2026-03-05T10:26:00Z",
  "senderId": "user-def-456",
  "chatRoomId": 5,
  "content": {
    "url": "https://storage.example.com/memes/funny-cat.gif",
    "caption": null
  }
}

Database Schema

Messages are persisted using the MessageEntity class:
public class MessageEntity
{
    public int Id { get; set; }
    public required MessageContentEntity Content { get; set; }
    
    [NotMapped]
    public string ContentJson => Content.ToJson(JsonSettings.MessageContentJsonSettings);
    
    public DateTime SentAt { get; set; }
    public string UserId { get; set; } = string.Empty;
    public ApplicationUser User { get; set; } = null!;
    public int ChatRoomId { get; set; }
    public ChatRoomEntity ChatRoom { get; set; } = null!;
}
The database entity includes a ContentJson computed property for serializing message content, and navigation properties for User and ChatRoom relationships.

Full-Text Search Capability

The message model supports full-text search, enabling users to:
  • Search message content across chat rooms
  • Find historical conversations
  • Locate specific information quickly
Search functionality is optimized for text content. Image captions are also searchable when present.

GraphQL Query Examples

Query Messages with Content Type Handling

query GetChatRoomMessages($chatRoomId: Int!) {
  messages(chatRoomId: $chatRoomId) {
    id
    sentAt
    senderId
    content {
      ... on TextMessageContent {
        text
      }
      ... on ImageMessageContent {
        url
        caption
      }
    }
  }
}

Subscribe to New Messages

subscription OnMessageSent($chatRoomId: Int!) {
  messageSent(chatRoomId: $chatRoomId) {
    id
    sentAt
    senderId
    chatRoomId
    content {
      ... on TextMessageContent {
        text
      }
      ... on ImageMessageContent {
        url
        caption
      }
    }
  }
}

Best Practices

  • Timestamps: Always use UTC for SentAt to avoid timezone issues
  • Content Validation: Validate text length and image URLs before persisting
  • Authorization: Verify user membership in chat room before allowing message creation
  • Image Storage: Store images externally and reference by URL for better performance
  • Content Type Handling: Use GraphQL inline fragments to properly handle different content types

Build docs developers (and LLMs) love