We're going to build a simple leaderboard module from scratch. Users will be able to authenticate, submit leaderboard scores, and retrieve the top scores.
This will teach you how to build a simple leaderboard module from scratch & all related modules concepts including:
Create a module
Setup your database
Write scripts
Write a test
Make sure you've installed the Rivet CLI as described here.
This will create a directory modules/my_leaderboard and updated rivet.json to include the module. The
important files we'll look at are modules/my_leaderboard/db/schema.prisma,
modules/my_leaderboard/scripts/, and modules/my_leaderboard/tests/.
Edit your modules/my_leaderboard/db/schema.prisma file to look like this:
datasource db { provider = "postgresql" url = env("DATABASE_URL")}model Scores { id String @id @default(uuid()) @db.Uuid createdAt DateTime @default(now()) @db.Timestamp userId String @db.Uuid score Int @@index([score])}
modules/my_leaderboard/db/schema.prisma
model Scores creates a new table that will store all of the scores on the leadboard
id is the unique identifier for each score.
String is the type of the column
@id indicates that this is the primary key of the table
@default(uuid()) indicates that this column will be automatically set to a UUID when a new row is created
@db.Uuid indicates that this is stored as UUID type. UUIDs are universally unique (unlike integers) and take a small amount of storage (unlike strings).
createdAt is the time the score was created
DateTime is the type of the column
@default(now()) indicates that this column will be automatically set to the current time when a new row is created
@db.Timestamp indicates that this is stored as a timestamp type. This is a good way to store dates and times in a database.
userId is the user who submitted the score
String is the type of the column
@db.Uuid indicates that this is stored as UUID type.
score is the score itself
Int is the type of the column
@@index([score]) creates an index on the score column.
This means the database will keep track of the scores in order so we can query them efficiently.
This will rate limit the script to 1 request every 15 seconds. If the rate limit is exceeded, a rate_limit_exceeded error will be thrown. See the rate limit docs for more details.
You will be prompted to apply your schema changes to the database. Name the migration init (this name doesn't matter):
Migrate Database develop (my_leaderboard)Prisma schema loaded from schema.prismaDatasource "db": PostgreSQL database "my_leaderboard", schema "public" at "host.docker.internal:5432"? Enter a name for the new migration: › init
Tests are helpful for validating your module works as expected before running in to the issue down the road. Testing is optional, but strongly encouraged.
Heads up!
All modules provided in the default registry are thoroughly tested.
Create test
rivet create test my_leaderboard e2e
Command Line
Might be useful!
e2e stands for "end to end" test. E2E tests simulate real-world scenarios involving multiple parts of a system. These tend to be comprehensive and catch the most bugs.
Write test
Update modules/my_leaderboard/tests/e2e.ts to look like this:
import { test, TestContext } from "../module.gen.ts";import { assertEquals } from "https://deno.land/[email protected]/assert/mod.ts";import { faker } from "https://deno.land/x/[email protected]/mod.ts";test("e2e", async (ctx: TestContext) => { // Create user & token to authenticate with const { user } = await ctx.modules.users.createUser({}); const { token } = await ctx.modules.users.createUserToken({ userId: user.id, }); // Create some scores const scores = []; for (let i = 0; i < 10; i++) { const score = faker.random.number({ min: 0, max: 100 }); await ctx.modules.myLeaderboard.submitScore({ userToken: token.token, score: score, }); scores.push(score); } // Get top scores scores.sort((a, b) => b - a); const topScores = await ctx.modules.myLeaderboard.getTopScores({ count: 5 }); assertEquals(topScores.scores.length, 5); for (let i = 0; i < 5; i++) { assertEquals(topScores.scores[i].score, scores[i]); }});
modules/my_leaderboard/tests/e2e.ts
Run test
In the same terminal, run:
rivet module test
Command Line
You should see this output once complete:
...test logs...----- output end -----e2e ... ok (269ms)ok | 1 passed | 0 failed (280ms)