DEV Community

Cover image for Presenting Mondocks
Angel Daniel Munoz Gonzalez
Angel Daniel Munoz Gonzalez

Posted on

4 3

Presenting Mondocks

I'm a person that tries to ease other people's lives, if not at least my own. One of the things I found hard to do when I started moving to F# was the "lack of MongoDB support" which is up to some point is false, MongoDB provides it's own Driver which for all intends and purposes it's focused in C#'s OOP style it is quite similar to an ODM (Object Document Mapper) and while it's use is quite idiomatic for C#, using it from F# sometimes can be quite clunky, what I wanted when I was learning was to focus on learning F# and not focus on database schemas, or how to make the driver work in a foreign language... sadly there was not a lot I could do for it, so I moved on to SQL solutions like Zaid's Npgsql.FSharp library which is an amazing piece of tech if you include the Npgsql.FSharp.Analyzers 100% recommended. Today I finally feel able to contribute back something that can be useful for those node developers who are looking to learn F# next

GitHub logo AngelMunoz / Mondocks

An alternative way to interact with MongoDB databases from F# that allows you to use mongo-idiomatic constructs

Mondocks

nuget Binder

dotnet add package Mondocks.Net
# or for fable/nodejs
dotnet add package Mondocks.Fable

This library is based on the mongodb extended json spec and mongodb manual reference

https://docs.mongodb.com/manual/reference/mongodb-extended-json/ > https://docs.mongodb.com/manual/reference/command/

This library provides a set of familiar tools if you work with mongo databases and can be a step into more F# goodies, it doesn't prevent you from using the usual MongoDB/.NET driver so you can use them side by side. It also can help you if you have a lot of flexible data inside your database as oposed to the usual strict schemas that F#/C# are used to from SQL tools, this provides a DSL that allow you to create MongoDB Commands (raw queries) leveraging the dynamism of anonymous records since they behave almost like javascript objects Writing commands should be almost painless these commands produce a JSON string that can be utilized directly on your application or even…

Mondocks is a Mongo Command builder library (like a SQL builder library but for MongoDB) which focuses on producing JSON that is compatible with MongoDB's Extended Json Spec, it provides a set of helpers called Computation Expressions that create a Domain Specific Language that you can use to keep using the queries and objects that you may know how to handle already.
But enough text, let's see some code (don't forget to check the samples as well).

Installing this library is quite simple
dotnet install Mondocks

#r "nuget: Mondocks.Net"
open Mondocks.Queries
let filterByName (name: string) =
{| name = name |}
let filterbyNameAndId (name: string) (id: string) =
let nameFilter = filterByName name
{| nameFilter with owner = {| ``$oid`` = id |} |}
let getUsersByName (name: string) (pagination: int * int) =
let offset, take = pagination
find "places" {
filter (filterByName name)
skip offset
limit take
}
let getPlacesByNameAndOwner (name: string) (owner: string) (pagination: int * int) =
let offset, take = pagination
find "users" {
filter (filterbyNameAndId name owner)
skip offset
limit take
}
let getMikes = getUsersByName "Mike" (0, 10)
let getPlacesFromMike =
// I'll use a fake ObjectId just to show
getPlacesByNameAndOwner "Travel Points" "5fc32b8456e87e4f021f43e2" (0, 10)
printfn $"getMikes Command: %s{getMikes}\ngetPlacesFromMike: %s{getPlacesFromMike}"
/// prints to the console
/// getMikes Command: {"find":"places","filter":{"name":"Mike"},"skip":0,"limit":10}
/// getPlacesFromMike: {"find":"users","filter":{"name":"Travel Points","owner":{"$oid":"5fc32b8456e87e4f021f43e2"}},"skip":0,"limit":10}
view raw read.fsx hosted with ❤ by GitHub

NOTE: you can run that with F# Interactive, download the file with the name find.fsx and run dotnet fsi ./find.fsx
NOTE: you also need to use the MongoDB.Driver library to execute these commands since Mondocks only produces JSON

  new MongoClient(URL)
        .GetDatabase(dbname)
        .RunCommand(JsonCommand(mycommand))

Which also means you can use it side by side with the usual MongoDB.Driver's API so it's a win-win you're not sacrificing anything 😁

In the sample above we're leveraging anonymous records from F# to create MongoDB queries since they behave pretty much like Javascript Objects we can even create new definitions from existing anonymous records similar to Object Spread in javascript (check filterbyNameAndId).

Let's move up to the update, updates are quite simple as well

#r "nuget: Mondocks.Net"
open System
open Mondocks.Types
open Mondocks.Queries
type Book =
{ _id: string;
author: string;
// other potential fields
}
let filterbyISBN (isbn: string) =
{| _id = isbn |}
let updateBook (isbn: string) (updateFields: Book) =
// updateFields can be a record like this { _id = "977877"; author = "Updated Author" }
update "books" {
updates [ { q = filterbyISBN isbn
u = updateFields
multi = Some false
upsert = Some false
collation = None
arrayFilters = None
hint = None } ]
}
let multipleUpdates(updates: seq<'TFilter * 'TUpdate>) =
let updateDefs =
updates
|> Seq.map(fun (filter, update) ->
{ q = filter
u = update
multi = Some false
upsert = Some true
collation = None
arrayFilters = None
hint = None })
update "places" {
updates updateDefs
}
let updateFranksBookCmd =
// suppose the author was previously just Frank, and now it will be Frank .D
updateBook "1234567890" { _id = "1234567890"; author = "Frank D." }
let updateTravelPointsCmd =
let updates =
[({| name = "Travel Point a" |},
{| name = "Travel Point A";
updatedAt = {|``$date`` = DateTime.Now.ToString("o") |} |} |> box)
({| name = "BFF's House" |},
// you can do updates with different data types, but you'll need to box them
{| name = "Not BFF's House";
notes = "I'll never talk to him again"
updatedAt = {|``$date`` = DateTime.Now.ToString("o") |} |} |> box)
({| name = "Convention Centre" |},
{| name = "Old Convention Centre";
address = "Old Street 123, Somewhere"
updatedAt = {|``$date`` = DateTime.Now.ToString("o") |} |} |> box)
]
multipleUpdates updates
printfn $"updateFranksBookCmd: {updateFranksBookCmd}\nupdateTravelPointsCmd: {updateTravelPointsCmd}"
// prints in the console
// updateFranksBookCmd: {"update":"books","updates":[{"q":{"_id":"1234567890"},"u":{"_id":"1234567890","author":"Frank D."},"upsert":false,"multi":false}]}
(*
I had to format this one
updateTravelPointsCmd: {
"update": "places",
"updates": [
{
"q": {
"name": "Travel Point a"
},
"u": {
"name": "Travel Point A",
"updatedAt": {
"$date": "2020-11-28T23:05:40.0396288-07:00"
}
},
"upsert": true,
"multi": false
},
{
"q": {
"name": "BFF's House"
},
"u": {
"name": "Not BFF's House",
"notes": "I'll never talk to him again",
"updatedAt": {
"$date": "2020-11-28T23:05:40.0396288-07:00"
}
},
"upsert": true,
"multi": false
},
{
"q": {
"name": "Convention Centre"
},
"u": {
"address": "Old Street 123, Somewhere",
"name": "Old Convention Centre",
"updatedAt": {
"$date": "2020-11-28T23:05:40.0396288-07:00"
}
},
"upsert": true,
"multi": false
}
]
}
*)
view raw update.fsx hosted with ❤ by GitHub

NOTE: there are certain BSON types that have to be represented in some specific ways to conform to the MongoDB JSON extended spec in this example you can see

{|``$date``: .... |}

for more info check here

I think that shows a little bit of how this library works and what you can expect from it.

You can check the samples to see how you can do things like count, distinct, delete, find, index creation, findAndModify, etc, etc. and also here's a small F# Restful API which uses this library

Frest

This is a sample API built with Falco, it uses mongodb as the database via Mondocks

Routes

let endpoints =
    [ post Urls.``/auth/login`` Value.Controller.login
      post Urls.``/auth/signup`` Value.Controller.signup
      get Urls.``/api/me`` Value.Controller.me
      get Urls.``/api/places`` Value.Controller.getPlaces
      post Urls.``/api/places`` Value.Controller.addPlaces
      put Urls.``/api/places/id`` Value.Controller.updatePlaces
      delete Urls.``/api/places/id`` Value.Controller.deletePlaces ]
Enter fullscreen mode Exit fullscreen mode

Organization

While names are almost meaningless to me I followed the Falco templates's rest default structure with a slight change of names.

  • Domain

    includes most of the base types there is to work with

  • Provider

    includes database access

  • Common

    utility functions that can be used accross models/controllers

  • Value

    Includes models and controllers along with its behaviors

  • Program

    definition of routes and the server's configuration

Basically as the nature of F# this is a top-down project which includes a Restful API with some protected routes…

If you know a MEAN Stack developer who could use a new language like F# I encourage you to show this library to them, perhaps that sparks interest. If you tried mongo in F# before but you didn't like it perhaps this is the time to give it a check again.

If you find a bug or have suggestions, feel free to raise a couple of issues or ping me on Twitter.

As always I hope you're having a great day and feel free to drop some comments below

Image of Datadog

How to Diagram Your Cloud Architecture

Cloud architecture diagrams provide critical visibility into the resources in your environment and how they’re connected. In our latest eBook, AWS Solution Architects Jason Mimick and James Wenzel walk through best practices on how to build effective and professional diagrams.

Download the Free eBook

Top comments (0)

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read more →

Retry later