Backend for the MAWAQIT Alexa skill: prayer time lookups, routines, widgets, and the Smart Azan (adhan) notification system.
The project is split into two independently deployed Serverless Framework services, plus the Alexa skill configuration itself:
| Path | Service name | Language | What it does |
|---|---|---|---|
lambda/ |
alexa |
JavaScript | Main skill backend: intent handling, prayer times, routines, widgets |
azan-lambda/ |
mawaqit-alexa-azan |
TypeScript | Smart Azan: scheduled adhan playback dispatch |
skill-package/ |
- | - | Alexa skill package: manifest (skill.json), interaction models, tasks, routine triggers — deployed via ask deploy |
utils/ |
- | - | One-off Python script for generating locale files |
The codebase is migrating to TypeScript one service at a time — see TypeScript.
alexa/
├── lambda/ # Main skill backend (service: alexa)
│ ├── index.js # indexHandler - handles Alexa skill requests
│ ├── trigger.js # triggerHandler - triggers the worker via EventBridge
│ ├── worker.js # workerHandler - processes queued Alexa events from SQS
│ ├── handlers/ # Intent handlers, DynamoDB, Alexa APIs, etc.
│ ├── prompts/ # Locale-specific prompt strings (en, fr, de)
│ ├── aplDocuments/ # Alexa Presentation Language (APL) templates
│ ├── tests/ # Jest tests for this service
│ ├── env.json # Non-secret per-stage config, read by serverless.yml
│ └── serverless.yml
├── azan-lambda/ # Smart Azan dispatcher (TypeScript, service: mawaqit-alexa-azan)
│ ├── src/handlers/ # Lambda entry point + one file per Alexa directive
│ ├── src/alexa/ # Smart Home response builder, error envelope, constants
│ ├── src/services/ # I/O: Amazon OAuth2, DynamoDB, SSM secrets
│ ├── src/types/ # Interfaces only — one file per domain
│ ├── src/logging/ # The Powertools logger instance
│ ├── tests/ # Jest tests for this service
│ ├── env.json
│ └── serverless.yml
├── skill-package/ # Alexa skill package, deployed via `ask deploy`
│ ├── skill.json # Skill manifest (endpoints, publishing info)
│ ├── interactionModels/ # custom/<locale>.json — one voice model per locale
│ ├── tasks/ # Custom task definitions (PlayAdhaan)
│ └── routines/ # Ready-made routine triggers
├── ask-resources.json # ASK CLI deploy config (points at skill-package/)
├── tsconfig.json # TypeScript config for the whole workspace
└── utils/ # create_locale_files.py (locale scaffolding script)
- Node.js 22.x (matches the Lambda runtime —
nodejs22.x) - pnpm 9+ —
corepack enable(ornpm install -g pnpm); the repo is a pnpm workspace
That is all you need to install dependencies and run the test suite. Deploying the services and managing the live skill require additional tooling and MAWAQIT infrastructure access — see Deployment.
The repo is a pnpm workspace: the root holds the shared tooling (eslint, prettier, jest) and the lambda and azan-lambda services are workspace packages. A single install at the root sets up all three:
pnpm installazan-lambda is TypeScript; lambda is still JavaScript. Both build and test
from the same root tooling, so the two can coexist indefinitely and the rest of
the codebase can be migrated service by service.
pnpm typecheck # tsc --noEmit over every TypeScript fileThings worth knowing before you touch the TypeScript:
- Nothing compiles to disk.
tsconly ever type-checks. Serverless v4 bundles the.tshandlers with esbuild at deploy time, and ts-jest compiles them for the test run — there is no build step and nodist/. - esbuild strips types without checking them, so a type error will bundle
and deploy happily.
pnpm typecheckis the only thing that catches it, which is why it runs in CI and in the pre-push hook. - The settings are strict, including
noUncheckedIndexedAccessandexactOptionalPropertyTypes.anyis banned by lint rather than by convention (@typescript-eslint/no-explicit-any), and linting is type-aware, so it also catches unsafe values and floating promises. - Interfaces live in
src/types/, one file per domain, and hold no logic.smartHomeRequest.tsandsmartHomeResponse.tssplit the Alexa wire format in two on purpose: incoming fields are optional (untrusted input — a handler proves a field is there before reading it), outgoing fields are required (we build those, so they must be complete). Keep that asymmetry. - Modules use
import/exportonly. There is norequireanywhere inazan-lambda, and the root configs (eslint.config.mjs,jest.config.mjs) are ESM too. CommonJS is only the output format esbuild emits for the Lambda runtime — an artifact detail, never something you write. - Logging goes through
src/logging/logger(AWS Lambda Powertools), neverconsole— a lint rule enforces it. Every line then carries the Lambda request id, which is the only way to follow one invocation through a log stream shared with every concurrent one. Verbosity is set byLOG_LEVEL. Secrets are never logged: only whether a token was present.
| Directory | Holds |
|---|---|
src/handlers/ |
dispatcher.ts (entry: validate + route), one file per directive |
src/alexa/ |
Response builder, error envelope, and the Alexa string constants |
src/services/ |
Everything that does I/O: amazonAuth, azanUsers, secrets |
src/types/ |
Interfaces and type aliases only |
src/logging/ |
The configured logger — the only place console is allowed |
Migrating another service means adding its directories to include in
tsconfig.json and renaming its files; the jest, eslint, and CI wiring already
handles both languages.
Tests run with Jest from the repo root and live next to the
code they cover, in lambda/tests/ (.test.js) and azan-lambda/tests/
(.test.ts). A single run covers both.
pnpm test # run the whole suite
pnpm test:watch # re-run on changeBoth services log verbosely on every code path, so the suite silences them.
VERBOSE_LOGS=1 pnpm test restores the output when you are debugging a failure.
VS Code/Cursor: install the recommended Jest extension, then reload the window if the Tests icon doesn't appear.
JetBrains IDEs need no additional setup.
pnpm install at the root also installs a husky pre-push hook that runs lint, then the typecheck, then the test suite, and blocks the push if any of them fails. There is nothing else to configure. Register it with:
pnpm run prepareContributions are welcome. To propose a change:
- Fork the repo and create a branch.
- Make your change with tests covering it, and keep the existing suite green
(
pnpm test, pluspnpm typecheckif you touched TypeScript). Lint, typecheck, and tests also run automatically on push via the pre-push hook and in CI. - Open a pull request describing what you changed and why.
You can work on the entire codebase — handlers, prayer-time logic, routines, prompts, interaction models, and tests — with only Node.js installed. Deploying to live infrastructure is handled by the MAWAQIT team.
Deploying the Lambda services, configuring AWS / Alexa credentials, managing secrets, and updating the live interaction model are handled internally by the MAWAQIT team and are not required to contribute code or tests.
➡️ MAWAQIT team: see the internal deployment and infrastructure runbook