Mongo Realm serverless functions don't support TypeScript natively, this article describes how to create an environment that allows writing functions using TypeScript.
The process of enabling TS support consist of the following steps:
- ๐ฒ Moving configuration files to Github repository
- ๐ Creating a TypeScript build chain
- ๐ฉ Add type declarations
- ๐ Enabling Github Automatic Deployments
- ๐ Enforce consistency with husky hooks
- ๐งช (Optional) Add CI/CD workflow using Github Actions
Mongo Realm doesn't support TypeScript natively, and we don't want to change that. Instead, we leverage the beauty of TypeScriptโโthe fact that TS produces JavaScript files during the transpilation process. That output JS files can be easily consumed by Mongo Realm. In other words, adding support for TypeScript comes down to structuring our codebase, and adding one extra step to our build chain.
๐ฒ Moving configuration files to Github repository
Mongo Realm allows exporting/importing all project configuration files, which is great for versioning and collaborative development. To automate the process, we can use Github Automatic Deployment to sync our codebase with the Mongo Realm application.
First, we need to create a Github repo (we use handy gh cli tool, but you can create it manually on Github website)
gh repo create <name-of-the-repo>
cd <name-of-the-repo>
Now, make sure you have installed and authenticated real-cli
, if not, follow these steps.
# download your realm app configuration files
realm-cli export \
--app-id <realm-app-id> \
--output myRealmApp \
--for-source-control
# move downloaded content and remove empty directory
mv myRealmApp/* . && rm -r myRealmApp
# commit all changes to GitHub repo
git add .
git commit -m "initial commit"
git push
๐ Creating TypeScript buildchain
Now we need to add a buildchain for producing JavaScript files from our TypeScript sources. We create a src/
directory that keeps our TypeScript version of the project source files.
It's important to match the directories structure of Mongo Realm application,
mkdir src
cp -r functions src
If we want to create a function called testFunc
we will create a directory src/functions/testFunc
with source.ts
and config.json
files.
// src/functions/testFunc/source.ts
exports = (test: string) => {
return test.toUpperCase()
}
// src/functions/testFunc/config.json
{
"name": "testFunc",
"private": false
}
Now, we need to transpile these source files to JavaScript equivalent. We create a Nodejs project at a project root level and add typescript packages.
npm init -y
npm i -D typescript @types/node
npx tsc --init
In tsconfig.json
we specify
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "./",
"rootDir": "./src",
"resolveJsonModule": true,
"moduleResolution": "node",
},
"include": ["src/**/*", "src/**/*.json"],
}
Mongo Realm supports ES6+ so we set the target
accordingly. Merging functions from src to root level is achieved with outDir
and rootDir
. Resolving JSONs is required because we want our config.json
files to be copied from src
to outDir
directory.
Now we are ready to execute transpilation with npx tsc
$ tree functions
functions
โโโ testFunc
โโโ config.json
โโโ source.js
$ cat functions/testFunc/source.js
exports = function (test) {
return test.toUpperCase();
};
At this point, our JavaScript function is generated and ready to be deployed.
๐ฉ Add type declarations
Realm does not allow us to use ES Modulesโโtherefore we can not import types, what we can do instead is to create type declarations that are automatically consumed by TypeScript compiler.
For instance, to add support for global context
object, create /src/types/realm.d.ts
file with the following content
/// <reference types="mongodb" />
type Services = {
(name: "mongodb-atlas") : import("mongodb").MongoClient;
// add your other services here
(name: string) : any;
}
declare global {
namespace context {
const services: {
get: Services;
};
const user: {
id: string;
type: "normal" | "server" | "system";
data: object;
custom_data: object;
identities: any[];
};
const functions: {
execute: (name: string, ...args: any[]) => any;
};
const environment: {
tag: string;
};
const values: {
get: (value) => any;
};
}
}
Instead of using import syntax, we use the /// <reference
directive for importing mongodb type declarations.
Lastly, install mongodb package
npm i mongodb
And enjoy the strict typing of context
object.
// src/functions/testFunc/source.ts
exports = (test: string) => {
const client: MongoClient = context.services.get("mongo-atlas")
// TypeScript correctly detect MongoClient type.
return test.toUpperCase()
}
๐ Enabling Github Automatic Deployments
We want our Github repo to be the single source of truth, to achieve that, we need Realm to sync with our repo. Fortunately, it's easy with the Github Automatic Deployments feature.ย
Now each time, you make a push to your repo, the changes will be automatically deployed to your Realm application.
๐ Enforce consistency with husky hooks
Right now, you have to remember to transpile your TypeScript source code before each push to the Github repo. If you forget about it, the changes won't take effect, since your /functions
directory contains out-dated JavaScript files. Luckily, husky comes to the rescueโโit allows to set hook actions that will be triggered before each commit/push.
npm i -D husky
npx husky install
npx husky add pre-commit "npx tsc && git add functions"
Now each time we execute git commit
, husky will execute tsc
for us, making sure that the src/
and /functions
are synchronized before pushing a change to the repoโโand so, deploying to Realm app.
๐งช (Optional) Add CI/CD workflow using Github Actions
As the icing on the cake, we can add CI/CD automation workflow. Since there is already a great resource on how to achieve it, I will just leave the link to the repo https://github.com/mongodb-developer/SocialStats.
I personally prefer Github Actions instead of Jenkins, if you are interested in achieving the same workflow using GH Actions, please let me know in the comments, and I will extend this article.
๐งโ๐ซ Conclusions
The Mongo Realm serverless functions infrastructure is still in the early stage, in contrast to e.g. Firebase (Google Cloud Functions) that supports TypeScript functions natively. Yet, in this article, we've shown that by preparing the development environment it is possible.
Personally, I believe that in the future realm-cli
will handle this whole process for us (similarly how firebase
cli does).
I hope this article was useful and saved you some time. ๐
Top comments (5)
This is a great solution and helped me a lot. However, I had difficulty with one part. I'm new to typescript so others may not struggle as much, but thought it could help someone.
When adding type declarations in the d.ts file, I found they weren't recognised using the exact code in the Add type declarations section. I needed to declare them as a global namespace to fix it.
So instead of this:
I used this:
Since it's declared globally, the context variables are recognised by the compiler. In my project I also use the context.environment.tag and context.values features, so I added in handling for these as well within the global context namespace, as below.
Hope this helps someone. Thanks Stanislaw for your article, saved me a massive headache.
Thank you, I've added your improvements :)
Thanks for sharing!
However manually providing type definitions is a nightmare, especially things like
cluster.db('x').collection('x').findOneAndUpdate
... You can't even borrow definitions frommongodb
node driver because they are slightly different here and there :(I assume it's something you have already figured out yourself, but I've added the "๐ฉ Add type declarations" subsection, it's not perfect but isn't that bad either.
Cannot find name 'context'.ts(2304)