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: '[email protected]',
  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', '[email protected]')
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', '[email protected]')
  .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: '[email protected]' },
  { 
    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: '[email protected]' },
    {
      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:

Models

Deep dive into model definitions, built-in properties, and conventions

Query Builder

Master the query builder with advanced filtering and sorting techniques

Relationships

Learn about defining and querying relationships between models

Aggregations

Explore aggregation functions and custom pipelines for data analysis

Security

Understand how Esix protects against NoSQL injection attacks

Testing

Set up testing with mock adapters and test databases

Build docs developers (and LLMs) love