Skip to main content

Overview

This quickstart guide will walk you through building a complete blogging application with Esix. You’ll learn how to define models, perform CRUD operations, query data, and work with relationships. By the end of this tutorial, you’ll have a working blog application with authors, posts, and the ability to query, filter, and aggregate data.

Prerequisites

Before you begin, make sure you have:
  • Node.js 20 or higher installed
  • MongoDB 4.0+ running locally or a MongoDB Atlas account
  • Basic TypeScript knowledge

Installation

Install Esix and MongoDB driver:
npm install esix mongodb

Configuration

Set your MongoDB connection string as an environment variable:
export DB_URL=mongodb://localhost:27017/blog
For MongoDB Atlas:
export DB_URL=mongodb+srv://username:password@cluster.mongodb.net/blog
That’s it! No configuration files, no decorators—just set your connection string and you’re ready to go.

Define Your Models

Create a file called models.ts and define your data models by extending BaseModel:
models.ts
import { BaseModel } from 'esix'

class Author extends BaseModel {
  public name = ''
  public email = ''
  public bio = ''
  public isActive = true

  // Define relationship to posts
  posts() {
    return this.hasMany(Post, 'authorId')
  }
}

class Post extends BaseModel {
  public title = ''
  public content = ''
  public authorId = ''
  public tags: string[] = []
  public views = 0
  public publishedAt: Date | null = null
}

export { Author, Post }
Every model automatically includes id, createdAt, and updatedAt fields. You don’t need to define them.

Create Records

Let’s create some authors and posts:
import { Author, Post } from './models'

// Create an author
const author = await Author.create({
  name: 'Jane Doe',
  email: 'jane@example.com',
  bio: 'Technology writer and blogger'
})

console.log(`Created author with ID: ${author.id}`)

// Create posts for this author
const post1 = await Post.create({
  title: 'Getting Started with TypeScript',
  content: 'TypeScript is a powerful superset of JavaScript...',
  authorId: author.id,
  tags: ['typescript', 'programming'],
  publishedAt: new Date()
})

const post2 = await Post.create({
  title: 'MongoDB Best Practices',
  content: 'Learn how to structure your MongoDB databases...',
  authorId: author.id,
  tags: ['mongodb', 'database'],
  publishedAt: new Date()
})

console.log(`Created ${2} posts`)

Query Data

Now let’s retrieve and filter our data:

Find by ID

// Find a specific author
const foundAuthor = await Author.find(author.id)
console.log(`Found: ${foundAuthor?.name}`)

// Find a specific post
const foundPost = await Post.find(post1.id)
console.log(`Found: ${foundPost?.title}`)

Find by Field

// Find author by email
const author = await Author.findBy('email', 'jane@example.com')
console.log(`Author: ${author?.name}`)

Get All Records

// Get all authors
const allAuthors = await Author.all()
console.log(`Total authors: ${allAuthors.length}`)

// Get all posts
const allPosts = await Post.all()
console.log(`Total posts: ${allPosts.length}`)

Filter with Where Clauses

Use the where() method to filter records:

Equality Filters

// Find active authors
const activeAuthors = await Author.where('isActive', true).get()

// Find posts with specific tag
const typescriptPosts = await Post.where('tags', ['typescript', 'programming']).get()

Comparison Operators

Esix supports all standard comparison operators:
// Posts with more than 100 views
const popularPosts = await Post.where('views', '>', 100).get()

// Posts with at least 50 views
const moderatelyPopular = await Post.where('views', '>=', 50).get()

// Posts with fewer than 10 views
const lowViewPosts = await Post.where('views', '<', 10).get()

// Posts that are not null
const publishedPosts = await Post.where('publishedAt', '!=', null).get()

Chaining Conditions

Combine multiple conditions for complex queries:
// Find published posts with high views
const topPublishedPosts = await Post
  .where('publishedAt', '!=', null)
  .where('views', '>', 100)
  .get()

// Find active authors with specific email domain
const companyAuthors = await Author
  .where('isActive', true)
  .where('email', 'jane@example.com')
  .get()

Array Queries

Query records where a field matches any value in an array:
// Find posts by multiple IDs
const specificPosts = await Post
  .whereIn('id', [post1.id, post2.id])
  .get()

// Find posts excluding certain tags
const filteredPosts = await Post
  .whereNotIn('tags', ['draft', 'archived'])
  .get()

Sorting and Pagination

Control the order and number of results:

Sort Results

// Latest posts first
const latestPosts = await Post
  .orderBy('createdAt', 'desc')
  .get()

// Most viewed posts
const topPosts = await Post
  .orderBy('views', 'desc')
  .get()

// Alphabetically by title
const alphabeticalPosts = await Post
  .orderBy('title', 'asc')
  .get()

Limit and Skip

// Get first 10 posts
const firstPage = await Post.limit(10).get()

// Get second page (posts 11-20)
const secondPage = await Post.skip(10).limit(10).get()

// Get top 5 most viewed posts
const top5 = await Post
  .orderBy('views', 'desc')
  .limit(5)
  .get()

Get First Result

// Get the first matching post
const firstPublished = await Post
  .where('publishedAt', '!=', null)
  .orderBy('publishedAt', 'asc')
  .first()

console.log(`First published post: ${firstPublished?.title}`)

Update Records

Modify existing records using the save() method:
// Find a post
const post = await Post.find(post1.id)

if (post) {
  // Update properties
  post.views = post.views + 1
  post.title = 'Getting Started with TypeScript (Updated)'
  
  // Save changes
  await post.save()
  
  console.log(`Updated post. Views: ${post.views}`)
  console.log(`Updated at: ${post.updatedAt}`)
}
The save() method automatically updates the updatedAt timestamp.

Delete Records

Remove records using the delete() method:
// Find and delete a post
const postToDelete = await Post.find(post2.id)

if (postToDelete) {
  await postToDelete.delete()
  console.log('Post deleted successfully')
}

Work with Relationships

Use the hasMany() method to query related records:
// Get all posts by an author
const author = await Author.find(author.id)
const authorPosts = await author.posts().get()

console.log(`${author.name} has ${authorPosts.length} posts`)

// Get only published posts by this author
const publishedPosts = await author
  .posts()
  .where('publishedAt', '!=', null)
  .get()

// Get the author's most popular posts
const popularPosts = await author
  .posts()
  .where('views', '>', 50)
  .orderBy('views', 'desc')
  .limit(5)
  .get()

// Count author's posts
const postCount = await author.posts().count()
console.log(`Total posts: ${postCount}`)

Aggregations

Perform calculations across your data:

Count

// Count all posts
const totalPosts = await Post.count()

// Count published posts
const publishedCount = await Post
  .where('publishedAt', '!=', null)
  .count()

console.log(`${publishedCount} of ${totalPosts} posts are published`)

Sum

// Total views across all posts
const totalViews = await Post.sum('views')
console.log(`Total views: ${totalViews}`)

// Total views for an author's posts
const authorViews = await author.posts().sum('views')
console.log(`Author's total views: ${authorViews}`)

Average

// Average views per post
const avgViews = await Post.average('views')
console.log(`Average views per post: ${avgViews}`)

// Average for published posts only
const avgPublishedViews = await Post
  .where('publishedAt', '!=', null)
  .average('views')

Min and Max

// Lowest and highest view counts
const minViews = await Post.min('views')
const maxViews = await Post.max('views')

console.log(`View range: ${minViews} to ${maxViews}`)

Percentiles

// 95th percentile of post views
const p95Views = await Post.percentile('views', 95)
console.log(`95% of posts have fewer than ${p95Views} views`)

// Median views
const medianViews = await Post.percentile('views', 50)
console.log(`Median views: ${medianViews}`)

Extract Field Values

// Get all post titles
const titles = await Post.pluck('title')
console.log('All titles:', titles)

// Get all unique tags (you'll need to flatten the array)
const allTags = await Post.pluck('tags')
const uniqueTags = [...new Set(allTags.flat())]
console.log('Unique tags:', uniqueTags)

First or Create

Find an existing record or create it if it doesn’t exist:
// Find author by email, create if not found
const author = await Author.firstOrCreate(
  { email: 'john@example.com' },
  { 
    name: 'John Smith',
    bio: 'Software developer',
    isActive: true
  }
)

console.log(author.createdAt === author.updatedAt 
  ? 'Created new author' 
  : 'Found existing author')

// When attributes aren't provided, filter is used as attributes
const defaultPost = await Post.firstOrCreate({
  title: 'Welcome Post',
  content: 'Welcome to our blog!',
  authorId: author.id,
  tags: ['welcome']
})

Custom Aggregations

For complex queries, use MongoDB’s aggregation pipeline directly:
// Group posts by author and count
const postsByAuthor = await Post.aggregate([
  {
    $group: {
      _id: '$authorId',
      count: { $sum: 1 },
      totalViews: { $sum: '$views' }
    }
  },
  {
    $sort: { count: -1 }
  }
])

console.log('Posts by author:', postsByAuthor)

// Get most common tags
const tagStats = await Post.aggregate([
  { $unwind: '$tags' },
  {
    $group: {
      _id: '$tags',
      count: { $sum: 1 }
    }
  },
  { $sort: { count: -1 } },
  { $limit: 5 }
])

console.log('Top 5 tags:', tagStats)

Complete Example

Here’s a complete working example that puts it all together:
blog.ts
import { BaseModel } from 'esix'

// Define models
class Author extends BaseModel {
  public name = ''
  public email = ''
  public bio = ''
  public isActive = true

  posts() {
    return this.hasMany(Post, 'authorId')
  }
}

class Post extends BaseModel {
  public title = ''
  public content = ''
  public authorId = ''
  public tags: string[] = []
  public views = 0
  public publishedAt: Date | null = null
}

async function main() {
  // Create an author
  const author = await Author.firstOrCreate(
    { email: 'jane@example.com' },
    {
      name: 'Jane Doe',
      bio: 'Technology writer and blogger',
      isActive: true
    }
  )

  console.log(`Author: ${author.name} (${author.id})`)

  // Create posts
  const post1 = await Post.create({
    title: 'Getting Started with TypeScript',
    content: 'TypeScript is a powerful superset of JavaScript that adds static typing...',
    authorId: author.id,
    tags: ['typescript', 'programming', 'tutorial'],
    views: 150,
    publishedAt: new Date()
  })

  const post2 = await Post.create({
    title: 'MongoDB Best Practices',
    content: 'Learn how to structure your MongoDB databases for optimal performance...',
    authorId: author.id,
    tags: ['mongodb', 'database', 'tutorial'],
    views: 230,
    publishedAt: new Date()
  })

  const post3 = await Post.create({
    title: 'Advanced TypeScript Patterns',
    content: 'Dive deep into advanced TypeScript patterns and techniques...',
    authorId: author.id,
    tags: ['typescript', 'advanced', 'patterns'],
    views: 89,
    publishedAt: null // Draft post
  })

  // Query published posts
  console.log('\n=== Published Posts ===')
  const publishedPosts = await Post
    .where('publishedAt', '!=', null)
    .orderBy('views', 'desc')
    .get()

  publishedPosts.forEach(post => {
    console.log(`- ${post.title} (${post.views} views)`)
  })

  // Get author's stats
  console.log('\n=== Author Stats ===')
  const totalPosts = await author.posts().count()
  const totalViews = await author.posts().sum('views')
  const avgViews = await author.posts().average('views')

  console.log(`Total posts: ${totalPosts}`)
  console.log(`Total views: ${totalViews}`)
  console.log(`Average views: ${avgViews.toFixed(1)}`)

  // Find most popular post
  console.log('\n=== Most Popular Post ===')
  const topPost = await author
    .posts()
    .where('publishedAt', '!=', null)
    .orderBy('views', 'desc')
    .first()

  if (topPost) {
    console.log(`${topPost.title} - ${topPost.views} views`)
  }

  // Update a post
  console.log('\n=== Updating Post ===')
  const postToUpdate = await Post.find(post1.id)
  if (postToUpdate) {
    postToUpdate.views += 25
    await postToUpdate.save()
    console.log(`Updated views: ${postToUpdate.views}`)
  }

  // Get tag statistics
  console.log('\n=== Tag Statistics ===')
  const tagStats = await Post.aggregate([
    { $match: { publishedAt: { $ne: null } } },
    { $unwind: '$tags' },
    {
      $group: {
        _id: '$tags',
        count: { $sum: 1 },
        totalViews: { $sum: '$views' }
      }
    },
    { $sort: { totalViews: -1 } }
  ])

  tagStats.forEach((stat: any) => {
    console.log(`${stat._id}: ${stat.count} posts, ${stat.totalViews} views`)
  })
}

main().catch(console.error)
Run it:
npx tsx blog.ts

Next Steps

Now that you’ve built your first Esix application, explore these topics to learn more: