docs/developing/open-source-contribution/contributors-guide.mdx
Cal.com is structured as a monorepo using Turborepo. Cal.com is built on NextJS with Typescript, PostgreSQL database and the interactions with the database are carried out using Prisma. We use TRPC for end-to-end typesafe procedure calls to Prisma and utilize Zod for validation and type-safe parsing of API calls.
Here is what you need to be able to run Cal.com.
Cal.com can be set up and run on your machine locally. Please take a look at this quick guide for instructions on setting it up.
<iframe
width="560"
height="315"
title="Run cal.com locally"
src="https://www.loom.com/embed/2f9d16b0959c4f1eaa5b306300ea4deb"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
></iframe>
Cal.com makes use of Turborepo for a monorepo structure. This allows us to manage multiple code projects (like libraries, apps, services) and share code and resources effectively.
You can look at turbo.json to see how we use it. In simple terms,
pipeline section corresponds to a different part of the project. Understanding these will help in navigating the codebase.env entries under each task highlight the need for specific configuration settings, which you will need to set up in your development environment.@calcom/prismaTo ensure consistency and make files easy to fuzzy-find, we follow the naming conventions below for services, repositories, and other class-based files.
Repository suffix.Pattern:
Prisma<Entity>Repository.ts
Examples:
// File: PrismaAppRepository.ts
export class PrismaAppRepository { ... }
// File: PrismaMembershipRepository.ts
export class PrismaMembershipRepository { ... }
This avoids ambiguous filenames like app.ts and improves discoverability in editors.
Service class files must include the Service suffix.
File name should be in PascalCase, matching the exported class.
Keep naming specific — avoid generic names like AppService.ts.
Pattern:
<Entity>Service.ts
Examples:
// File: MembershipService.ts
export class MembershipService { ... }
// File: HashedLinkService.ts
export class HashedLinkService { ... }
Cal.com makes use of the TRPC calls for CRUD operations on the database by utilizing the useQuery() & useMutation() hooks. These are essentially wrappers around @tanstack/react-query and you can learn more about them here, respectively: queries, mutations.
Here’s an example usage of useQuery at cal.com:
const { data: webhooks } = trpc.viewer.webhook.list.useQuery(undefined, {
suspense: true,
enabled: session.status === "authenticated",
});
Here’s an example usage of useMutation at cal.com:
const createWebhookMutation = trpc.viewer.webhook.create.useMutation({
async onSuccess() {
showToast(t("webhook_created_successfully"), "success");
await utils.viewer.webhook.list.invalidate();
router.back();
},
onError(error) {
showToast(`${error.message}`, "error");
},
});
Both the examples are taken from packages/features/webhooks/pages/webhook-new-view.tsx file. We use the useQuery hook for fetching data from the database. On the contrary, the useMutation hook is used for Creating/Updating/Deleting data from the database.
The path prior to the useQuery or useMutation hook represents the actual path of the procedure: in this case, packages/trpc/server/routers/viewer/webhook. You will find create.handler.ts in case of
trpc.viewer.webhook.create.useMutation()
& list.handler.ts in case of
trpc.viewer.webhook.list.useQuery()
in the path mentioned above. There is usually a schema file in the same directory for each handler as well (name.schema.ts). This is generally how we make use of the TRPC calls in the cal.com repo.
Our API V1 uses zod validations to keep us typesafe and give us extensive parsing control and error handling. These validations reside in /apps/api/lib/validations. We also have helper and utility functions which are used primarily within our API endpoints. Here’s an example usage of our zod validation in action with the respective API endpoint:
export const schemaDestinationCalendarBaseBodyParams = DestinationCalendar.pick({
integration: true,
externalId: true,
eventTypeId: true,
bookingId: true,
userId: true,
}).partial();
const schemaDestinationCalendarCreateParams = z
.object({
integration: z.string(),
externalId: z.string(),
eventTypeId: z.number().optional(),
bookingId: z.number().optional(),
userId: z.number().optional(),
})
.strict();
export const schemaDestinationCalendarCreateBodyParams = schemaDestinationCalendarBaseBodyParams.merge(
schemaDestinationCalendarCreateParams
);
And here’s the usage of this validation for parsing the request at the respective endpoint:
async function postHandler(req: NextApiRequest) {
const { userId, isAdmin, prisma, body } = req;
const parsedBody = schemaDestinationCalendarCreateBodyParams.parse(body);
This is the general approach of validation and parsing in our API submodule.
We’ve got an app store containing apps which complement the scheduling platform. From video conferencing apps such as Zoom, Google Meet, etc. to Calendar Apps such as Google Calendar, Apple Calendar, CalDAV, etc. and even payment, automation, analytics and other miscellaneous apps, it is an amazing space to improve the scheduling experience.
We have a CLI script in place that makes building apps a breeze. You can learn more about it here.
Be sure to set the environment variable NEXTAUTH_URL to the correct value. If you are running locally, as the documentation within .env.example mentions, the value should be http://localhost:3000.
In a terminal just run:
yarn test-e2e
To open the last HTML report run:
`yarn playwright show-report test-results/reports/playwright-html-report`
E2E test browsers not installed
If you face the following error when running yarn test-e2e:
You can resolve it by running:
npx playwright install
to download test browsers.