### Build and Start Application for Production Source: https://github.com/mondaycom/monday-code-quickstarts/blob/main/quickstart-node/README.md Compile TypeScript files and then start the application for production use. ```bash npm run build npm run start ``` -------------------------------- ### Install Project Dependencies Source: https://github.com/mondaycom/monday-code-quickstarts/blob/main/quickstart-node/README.md Navigate to the project directory and install required packages using npm. ```bash cd quickstart-node npm i ``` -------------------------------- ### Install Project Dependencies Source: https://github.com/mondaycom/monday-code-quickstarts/blob/main/quickstart-python/README.md Installs the required Python packages for the project using pip. ```bash pip install -r requirements.txt ``` -------------------------------- ### Install Project Dependencies Source: https://github.com/mondaycom/monday-code-quickstarts/blob/main/quickstart-php/README.md Installs required packages for the PHP project using Composer. Ensure Composer is set up locally. ```bash php composer.phar update ``` -------------------------------- ### Deployment with monday-code CLI Source: https://context7.com/mondaycom/monday-code-quickstarts/llms.txt Provides commands for the full deploy lifecycle using the `@mondaycom/apps-cli` tool, including installation, authentication, development mode, deployment, log streaming, and build processes. ```bash # Install the CLI globally npm install -g @mondaycom/apps-cli # Authenticate and link to your monday.com account mapps init # Development mode: starts server + creates a public tunnel (ngrok-like) # from inside quickstart-node/ npm run dev # Equivalent to: concurrently "npm run server" "mapps tunnel:create -p 3000" # Deploy to monday-code (first deploy ~20 min, subsequent ~3 min) # Run from inside any quickstart-* directory mapps code:push # CLI prompts: choose app → choose version → starts deploy pipeline # Output: "Your app is live at https://xxxxxxxx.monday-code.com" # Stream live logs from the deployed app mapps code:logs # Build TypeScript before a production start (Node.js only) npm run build && npm run start # Generate typed GraphQL helpers from the monday schema npm run fetch:generate # Runs: bash fetch-schema.sh && graphql-codegen # Output: src/generated/graphql.ts # Environment variables — set in monday-code dashboard, not in .env # Required env vars (set under General → Environment Variables): # MONDAY_OAUTH_CLIENT_ID # MONDAY_OAUTH_TOKEN_PATH # MONDAY_OAUTH_BASE_PATH # # Required secrets (set under General → Secrets): # MONDAY_SIGNING_SECRET # MONDAY_OAUTH_CLIENT_SECRET ``` -------------------------------- ### Run the Python Application Source: https://github.com/mondaycom/monday-code-quickstarts/blob/main/quickstart-python/README.md Starts the Flask application. The application will be accessible at http://0.0.0.0:8080. ```bash python main.py ``` -------------------------------- ### Python Flask Application Bootstrap Source: https://context7.com/mondaycom/monday-code-quickstarts/llms.txt Sets up a Flask application, registers blueprints for authentication, mail, and worker queues, and configures error handlers. Run locally using pip install -r requirements.txt and python main.py. ```python # quickstart-python/main.py import os from flask import Flask, jsonify from werkzeug.exceptions import HTTPException from errors import handle_internal_error, handle_general_http_exception, handle_base_error, BaseError, InternalServerError from routes import worker_queue_bp, mail_bp, auth_bp app = Flask(__name__) app.register_blueprint(worker_queue_bp) # POST /mndy-queue app.register_blueprint(mail_bp, url_prefix="/mail") # POST /mail/send app.register_blueprint(auth_bp, url_prefix="/auth") # GET /auth/, GET /auth/monday/callback app.register_error_handler(HTTPException, handle_general_http_exception) app.register_error_handler(InternalServerError, handle_internal_error) app.register_error_handler(BaseError, handle_base_error) @app.route("/") @app.route("/health") def health_check(): return jsonify({"message": "Healthy"}), 200 if __name__ == "__main__": app.run(host="0.0.0.0", port=int(os.environ.get("PORT", 8080))) # Run locally: # pip install -r requirements.txt # python main.py # → Running on http://0.0.0.0:8080 ``` -------------------------------- ### Run Application Locally Source: https://github.com/mondaycom/monday-code-quickstarts/blob/main/quickstart-php/README.md Starts the PHP application locally using Docker Compose and the built-in PHP web server. Access the application at http://0.0.0.0:8080. ```bash docker compose up ``` ```bash php -S 0.0.0.0:8080 ``` -------------------------------- ### Global Error Handler Middleware for Express Source: https://context7.com/mondaycom/monday-code-quickstarts/llms.txt Catches all errors in Express routes and returns a standardized JSON error body. Logs 5xx errors using LoggerService. Ensure http-errors are installed for helper exceptions. ```typescript // quickstart-node/src/middlewares/error.middleware.ts import { LoggerService } from '@services/monday-code'; import { HttpStatusCode } from 'axios'; const errorHandler = (err: any, req: Request, res: Response, next: NextFunction) => { const statusCode = err.status || HttpStatusCode.InternalServerError; const message = err.message || 'Internal Server Error'; // Log server errors and any error with .logging=true if (statusCode >= HttpStatusCode.InternalServerError || err.logging) { LoggerService.getInstance().error(message, err); } res.status(statusCode).json({ statusCode, // Mask internal error details from clients message: statusCode === HttpStatusCode.InternalServerError ? 'Internal Server Error' : message, }); }; export default errorHandler; // Error response shapes: // 400: { "statusCode": 400, "message": "Invalid state parameter" } // 401: { "statusCode": 401, "message": "Missing Authorization Token" } // 500: { "statusCode": 500, "message": "Internal Server Error" } // http-errors helpers used throughout routes: import { BadRequest, Unauthorized, InternalServerError, NotFound } from 'http-errors'; throw new BadRequest('User ID not found'); // → 400 throw new Unauthorized('Token has expired'); // → 401 throw new NotFound('Board not found'); // → 404 ``` -------------------------------- ### Initialize and Use EnvironmentVariablesService Source: https://context7.com/mondaycom/monday-code-quickstarts/llms.txt Singleton service to access environment variables configured in the monday-code dashboard. Throws an error by default if a requested key is not found. ```typescript import { EnvironmentVariablesManager } from '@mondaycom/apps-sdk'; export class EnvironmentVariablesService { private static instance: EnvironmentVariablesService; private mondayCodeEnvironmentVariablesManager = new EnvironmentVariablesManager(); static getInstance() { if (!this.instance) this.instance = new EnvironmentVariablesService(); return this.instance; } // options.throwOnUndefined=true (default) throws if key is absent get(key: string, options = { throwOnUndefined: true }): JsonValue | undefined { return this.mondayCodeEnvironmentVariablesManager.get(key, options); } // Returns all configured variable names (useful for debugging) getKeys(options?: { invalidate?: boolean }): string[] { return this.mondayCodeEnvironmentVariablesManager.getKeys(options); } } // Defined keys (set in monday-code dashboard under Environment Variables): export enum EnvironmentsKeys { MONDAY_OAUTH_CLIENT_ID = 'MONDAY_OAUTH_CLIENT_ID', MONDAY_OAUTH_TOKEN_PATH = 'MONDAY_OAUTH_TOKEN_PATH', MONDAY_OAUTH_BASE_PATH = 'MONDAY_OAUTH_BASE_PATH', } // Usage: const clientId = EnvironmentVariablesService.getInstance().get(EnvironmentsKeys.MONDAY_OAUTH_CLIENT_ID); // → "my-app-client-id" const allKeys = EnvironmentVariablesService.getInstance().getKeys(); // → ["MONDAY_OAUTH_CLIENT_ID", "MONDAY_OAUTH_TOKEN_PATH", "MONDAY_OAUTH_BASE_PATH"] ``` -------------------------------- ### Express App Entry Point Source: https://context7.com/mondaycom/monday-code-quickstarts/llms.txt Bootstraps the Express server, registers route groups, and attaches the global error-handler middleware. Ensure environment variables are loaded using `dotenv.config()` before server startup. ```typescript // quickstart-node/src/index.ts import express, { Express, Request, Response } from 'express'; import dotenv from 'dotenv'; import authRouter from '@routes/auth.route'; import errorHandler from '@middlewares/error.middleware'; import cookieParser from 'cookie-parser'; import mondayRouter from '@routes/monday.route'; import mondayQueueRouter from '@routes/monday-queue'; import mailRouter from '@routes/mail.route'; import { HttpStatusCode } from 'axios'; dotenv.config(); const app: Express = express(); const port = process.env.PORT || 3000; app.use(cookieParser()); app.use('/auth', authRouter); app.use('/monday', mondayRouter); app.use('/', mondayQueueRouter); app.use('/mail', mailRouter); app.use('/', (req: Request, res: Response) => { res.status(HttpStatusCode.Ok).send('Hello world!'); }); // Global error handler — must be the last middleware registered app.use(errorHandler); app.listen(port, () => { console.log(`[server]: Server is running at http://localhost:${port}`); }); ``` -------------------------------- ### View Live Logs on monday Code Source: https://github.com/mondaycom/monday-code-quickstarts/blob/main/README.md Run this command to view live logs for your deployed application on monday code. This is useful for debugging and monitoring your application's activity. ```bash mapps code:logs ``` -------------------------------- ### SecureStorageService Source: https://context7.com/mondaycom/monday-code-quickstarts/llms.txt Wraps the monday-code `SecureStorage` SDK class for encrypted, app-scoped storage. Accessed as a singleton and does not require a user token, suitable for sensitive data like OAuth tokens. ```APIDOC ## SecureStorageService — Encrypted App-Scoped Storage (Singleton) Wraps the monday-code `SecureStorage` SDK class. Unlike `StorageService`, it does not require a user token and is accessed as a singleton. Intended for sensitive data like OAuth tokens and user session state. ```typescript // quickstart-node/src/services/monday-code/secure-storage.service.ts import { SecureStorage } from '@mondaycom/apps-sdk'; export class SecureStorageService { private static instance: SecureStorageService; private mondayCodeSecureStorageManager = new SecureStorage(); static getInstance(): SecureStorageService { if (!this.instance) this.instance = new SecureStorageService(); return this.instance; } set(key: string, value: T): Promise { return this.mondayCodeSecureStorageManager.set(key, value); } get(key: string): Promise { return this.mondayCodeSecureStorageManager.get(key); } delete(key: string): Promise { return this.mondayCodeSecureStorageManager.delete(key); } } // Save OAuth token after callback: const secureStorage = SecureStorageService.getInstance(); await secureStorage.set('user_42', { mondayToken: { access_token: 'eyJ...' } }); // → true // Retrieve to authorize a downstream API call: const data = await secureStorage.get<{ mondayToken: { access_token: string } }>('user_42'); // → { mondayToken: { access_token: "eyJ..." } } // Delete on logout: await secureStorage.delete('user_42'); // → true ``` ``` -------------------------------- ### Run Application in Development Mode Source: https://github.com/mondaycom/monday-code-quickstarts/blob/main/quickstart-node/README.md Execute this command in the project directory to run the application in development mode. It also creates a tunnel using monday-apps-cli. ```bash npm run dev ``` -------------------------------- ### Deploy to monday code Source: https://github.com/mondaycom/monday-code-quickstarts/blob/main/quickstart-php/README.md Deploys the application to the monday code platform using the mapps CLI tool. ```bash mapps code:push ``` -------------------------------- ### StorageService Source: https://context7.com/mondaycom/monday-code-quickstarts/llms.txt Wraps the monday-code `Storage` SDK class for per-user key-value storage. Requires a valid monday access token and stores JSON-serializable values scoped to the app and user. ```APIDOC ## StorageService — Per-User Key-Value Storage Wraps the monday-code `Storage` SDK class. Requires a valid monday access token. Stores JSON-serializable values scoped to the app and user. Supports optional shared-access and versioned-read (`previousVersion`) modes. ```typescript // quickstart-node/src/services/monday-code/storage.service.ts import { Storage } from '@mondaycom/apps-sdk'; export class StorageService { private mondayCodeStorageManager; constructor(token: string) { if (!token) throw new Unauthorized('Missing access token'); this.mondayCodeStorageManager = new Storage(token); } // Store a value — returns { success: true, version: "v1" } set(key: string, value: T, options?: { shared?: boolean }): Promise { return this.mondayCodeStorageManager.set(key, value, options); } // Retrieve — returns { success: true, value: T | null, version: "v1" } get(key: string, options?: { shared?: boolean; previousVersion?: string }): Promise> { return this.mondayCodeStorageManager.get(key, options); } delete(key: string, options?: { shared?: boolean }): Promise { return this.mondayCodeStorageManager.delete(key, options); } } // Usage in a route handler: const mondayAccessToken = (await SecureStorageService.getInstance().get(userId))?.mondayToken?.access_token; const storage = new StorageService(mondayAccessToken); await storage.set('mailAddress', 'admin@example.com'); // → { success: true, version: "abc123" } const result = await storage.get('mailAddress'); // → { success: true, value: "admin@example.com", version: "abc123" } await storage.delete('mailAddress'); // → { success: true } ``` ``` -------------------------------- ### Encrypted App-Scoped Storage Service (Singleton) Source: https://context7.com/mondaycom/monday-code-quickstarts/llms.txt Wraps the monday-code SecureStorage SDK for encrypted app-scoped storage. Accessed as a singleton, it does not require a user token. Intended for sensitive data like OAuth tokens and user session state. ```typescript import { SecureStorage } from '@mondaycom/apps-sdk'; export class SecureStorageService { private static instance: SecureStorageService; private mondayCodeSecureStorageManager = new SecureStorage(); static getInstance(): SecureStorageService { if (!this.instance) this.instance = new SecureStorageService(); return this.instance; } set(key: string, value: T): Promise { return this.mondayCodeSecureStorageManager.set(key, value); } get(key: string): Promise { return this.mondayCodeSecureStorageManager.get(key); } delete(key: string): Promise { return this.mondayCodeSecureStorageManager.delete(key); } } // Save OAuth token after callback: const secureStorage = SecureStorageService.getInstance(); await secureStorage.set('user_42', { mondayToken: { access_token: 'eyJ...' } }); // → true // Retrieve to authorize a downstream API call: const data = await secureStorage.get<{ mondayToken: { access_token: string } }>('user_42'); // → { mondayToken: { access_token: "eyJ..." } } // Delete on logout: await secureStorage.delete('user_42'); // → true ``` -------------------------------- ### Define GraphQL Fragments, Queries, and Mutations Source: https://context7.com/mondaycom/monday-code-quickstarts/llms.txt Defines GraphQL fragments, queries, and mutations for use with monday-code services. Compatible with GraphQL codegen for typed variables and responses. Run 'npm run fetch:generate' to generate types. ```typescript import { gql } from 'graphql-request'; export const itemWithColumnValues = gql` fragment ItemWithColumnValues on Item { id column_values { id text type value } } `; // Fetches all items in a specific board group (paginated) export const getItemsInGroup = gql` ${itemWithColumnValues} query GetItemsInGroup($boardId: ID!, $groupId: String!) { boards(ids: [$boardId]) { groups(ids: [$groupId]) { items_page { items { ...ItemWithColumnValues } } } } } `; // Creates a new item and returns its ID export const createItem = gql` mutation CreateItem($boardId: ID!, $itemName: String!) { create_item(board_id: $boardId, item_name: $itemName) { id } } `; // Returns the ID of the authenticated user (sanity / auth check) export const getMe = gql` query GetMe { me { id } } `; // Generate typed variables + response types: // npm run fetch:generate // (runs: bash fetch-schema.sh && graphql-codegen) // Output: quickstart-node/src/generated/graphql.ts ``` -------------------------------- ### Run Server Only Source: https://github.com/mondaycom/monday-code-quickstarts/blob/main/quickstart-node/README.md If you only want to run your server without the tunnel, use this command. ```bash npm run server ``` -------------------------------- ### MondayService GraphQL API Operations Source: https://context7.com/mondaycom/monday-code-quickstarts/llms.txt This service wraps the monday SDK for common API operations. It retrieves the ApiClient from context or creates a new one with an explicit token. ```typescript // quickstart-node/src/services/monday.service.ts import { ApiClient } from '@mondaydotcomorg/api'; import { getValueFromExecutionContext } from '@utils/execution-context.utils'; export const UPDATED_MONDAY_API_VERSION = '2024-10'; // Retrieves the ApiClient from AsyncLocalStorage (set by setMondayApiClient middleware) // or creates a new one from an explicit token. const getMondayApiClient = (token?: string): ApiClient => token ? new ApiClient({ token, apiVersion: UPDATED_MONDAY_API_VERSION }) : getValueFromExecutionContext('mondayApiClient'); // GET /monday/me — returns the authenticated user's ID const getMe = async (token?: string) => getMondayApiClient(token).request(getMeQuery); // Response: { me: { id: "12345678" } } // Fetch all items in a board group, sorted descending by a date column const getLastItemInGroupByDate = async (boardId, groupId, dateColumnId, token?) => { const boards = await getMondayApiClient(token).request(getItemsInGroupQuery, { boardId, groupId }); const items = boards?.boards?.[0]?.groups?.[0]?.items_page?.items; if (!items?.length) throw new NotFound(`No items found in group ${groupId}`); const sorted = getSortedItemsByDateColumn(dateColumnId, items); if (!sorted.length) throw new NotFound(`No items with valid date in group ${groupId}`); return sorted[0]; // most recent item }; // Mutate a single column value on an item const changeColumnValue = async (value, itemId, boardId, columnId, token?) getMondayApiClient(token).operations.changeColumnValueOp({ boardId, itemId, columnId, value }); // Create a new item on a board const createItem = async (boardId: string, itemName: string, token?: string) => getMondayApiClient(token).request(createItemQuery, { boardId, itemName }); // Response: { create_item: { id: "9876543" } } export const MondayService = { getMondayApiClient, changeColumnValue, getLastItemInGroupByDate, createItem, getMe, isItemColumnValueDifferent, }; // --- HTTP route using MondayService --- // POST /monday/change-last-item-status // Body: { payload: { inputFields: { boardId, groupId, statusColumnId, dateColumnId, statusColumnValue } } } mondayRouter.post('/change-last-item-status', async (req, res) => { const { boardId, groupId, statusColumnId, dateColumnId, statusColumnValue } = req.body.payload.inputFields; const item = await MondayService.getLastItemInGroupByDate(boardId, groupId, dateColumnId); if (!MondayService.isItemColumnValueDifferent(item, statusColumnId, (v) => JSON.parse(v)?.index === statusColumnValue?.index)) { await MondayService.changeColumnValue(JSON.stringify(statusColumnValue), item.id, boardId, statusColumnId); } res.status(200).send('success'); }); ``` -------------------------------- ### Per-User Key-Value Storage Service Source: https://context7.com/mondaycom/monday-code-quickstarts/llms.txt Wraps the monday-code Storage SDK for per-user key-value storage. Requires a valid monday access token. Stores JSON-serializable values scoped to the app and user. Supports optional shared-access and versioned-read modes. ```typescript import { Storage } from '@mondaycom/apps-sdk'; export class StorageService { private mondayCodeStorageManager; constructor(token: string) { if (!token) throw new Unauthorized('Missing access token'); this.mondayCodeStorageManager = new Storage(token); } // Store a value — returns { success: true, version: "v1" } set(key: string, value: T, options?: { shared?: boolean }): Promise { return this.mondayCodeStorageManager.set(key, value, options); } // Retrieve — returns { success: true, value: T | null, version: "v1" } get(key: string, options?: { shared?: boolean; previousVersion?: string }): Promise> { return this.mondayCodeStorageManager.get(key, options); } delete(key: string, options?: { shared?: boolean }): Promise { return this.mondayCodeStorageManager.delete(key, options); } } // Usage in a route handler: const mondayAccessToken = (await SecureStorageService.getInstance().get(userId))?.mondayToken?.access_token; const storage = new StorageService(mondayAccessToken); await storage.set('mailAddress', 'admin@example.com'); // → { success: true, version: "abc123" } const result = await storage.get('mailAddress'); // → { success: true, value: "admin@example.com", version: "abc123" } await storage.delete('mailAddress'); // → { success: true } ``` -------------------------------- ### GraphQL Queries and Mutations Source: https://context7.com/mondaycom/monday-code-quickstarts/llms.txt Defines GraphQL fragments, queries, and mutations for interacting with the Monday.com API. These are compatible with GraphQL codegen for typed variables and responses. ```APIDOC ## GraphQL Query Definitions Defines the GraphQL fragments, queries, and mutations used by `MondayService`, compatible with GraphQL codegen for typed variables and responses. ```typescript // quickstart-node/src/queries.graphql.ts import { gql } from 'graphql-request'; export const itemWithColumnValues = gql` fragment ItemWithColumnValues on Item { id column_values { id text type value } } `; // Fetches all items in a specific board group (paginated) export const getItemsInGroup = gql` ${itemWithColumnValues} query GetItemsInGroup($boardId: ID!, $groupId: String!) { boards(ids: [$boardId]) { groups(ids: [$groupId]) { items_page { items { ...ItemWithColumnValues } } } } } `; // Creates a new item and returns its ID export const createItem = gql` mutation CreateItem($boardId: ID!, $itemName: String!) { create_item(board_id: $boardId, item_name: $itemName) { id } } `; // Returns the ID of the authenticated user (sanity / auth check) export const getMe = gql` query GetMe { me { id } } `; // Generate typed variables + response types: // npm run fetch:generate // (runs: bash fetch-schema.sh && graphql-codegen) // Output: quickstart-node/src/generated/graphql.ts ``` ``` -------------------------------- ### Python Mail Action Route Source: https://context7.com/mondaycom/monday-code-quickstarts/llms.txt Handles POST requests to send mail. It publishes the mail sending task to a background queue and responds immediately. Requires monday_token for storage operations. ```python # quickstart-python/routes/mail.py from flask import Blueprint, request from services import QueueService, StorageService, SecureStorage mail_bp = Blueprint('mail', __name__) @mail_bp.route('/send', methods=['POST']) @monday_request_auth async def send_mail(): user_id = request.session.get('userId') payload = request.get_json().get('payload') board_id = payload.get('inputFields').get('boardId', 'Example Board ID') content = f"Some trigger just ran! check the board - {board_id}" address = "admin.mail@example.com" monday_token = (await SecureStorage.get(user_id, default_value={})) \ .get('monday_token', {}).get('access_token') if not monday_token: raise InternalServerError('monday_token not found') storage_service = StorageService(monday_token) await storage_service.upsert('mail_address', address) # Offload to background queue — respond immediately to monday await QueueService.publish_message({ 'method': 'send_mail', 'content': content, 'user_token': monday_token }) return 'Received', 200 ``` ```bash # curl simulation of a monday automation trigger: # curl -X POST http://localhost:8080/mail/send \ # -H "Content-Type: application/json" \ # -H "Authorization: " \ # -d '{"payload": {"inputFields": {"boardId": "1234567890"}}}' # → "Received" ``` -------------------------------- ### OAuth2 Authorization Flow: Redirect and Callback Source: https://context7.com/mondaycom/monday-code-quickstarts/llms.txt Handles the two-leg OAuth2 flow for monday.com. The first part redirects users to monday.com for authorization, storing CSRF state. The second part handles the callback, validates the state, exchanges the code for an access token, and stores it. ```typescript // quickstart-node/src/routes/auth.route.ts — condensed usage illustration import { EnvironmentVariablesService, SecureStorageService } from '@services/monday-code'; import { JWTService } from '@services/jwt.service'; import { generateRandomUrlSafeString } from '@utils/url.utils'; import { EnvironmentsKeys } from '@shared/monday-auth-consts'; // Step 1 — GET /auth/ // monday.com calls this URL (set as Authorization URL in Feature Details). // If user already has a token in SecureStorage, redirect immediately. // Otherwise build the OAuth redirect URL and store a CSRF state cookie. authRouter.get('/', mondayRequestAuth, async (req, res) => { const userId = String(req.session?.userId); const backToUrl = req.session?.backToUrl; const userDetails = await SecureStorageService.getInstance().get(userId); if (userDetails?.mondayToken) return res.redirect(backToUrl); await SecureStorageService.getInstance().set(userId, { backToUrl }); const state = generateRandomUrlSafeString(16); // CSRF token const params = new URLSearchParams({ client_id: EnvironmentVariablesService.getInstance().get(EnvironmentsKeys.MONDAY_OAUTH_CLIENT_ID) as string, state, }); const redirectUrl = new URL( EnvironmentVariablesService.getInstance().get(EnvironmentsKeys.MONDAY_OAUTH_BASE_PATH) as string, ); redirectUrl.search = params.toString(); res.cookie('user_id', userId); res.cookie('state', state); return res.redirect(redirectUrl.toString()); // → Redirects to: https://auth.monday.com/oauth2/authorize?client_id=XXX&state=abc123 }); // Step 2 — GET /auth/monday/callback // monday.com redirects here with ?code=...&state=... // Validates state, exchanges code for token, stores token, redirects back. authRouter.get('/monday/callback', async (req, res) => { const code = req.query.code as string; const userId = req.cookies?.user_id; validateState(req); // throws BadRequest if state mismatch const mondayToken = await JWTService.getMondayToken(code); // mondayToken = { access_token: "eyJ...", token_type: "Bearer" } const backToUrl = (await SecureStorageService.getInstance().get(userId))?.backToUrl; await SecureStorageService.getInstance().set(userId, { mondayToken }); return res.redirect(backToUrl); }); ``` -------------------------------- ### Python Flask OAuth2 Auth Blueprint Source: https://context7.com/mondaycom/monday-code-quickstarts/llms.txt Implements the OAuth2 two-leg flow for Monday.com integration using Python Flask. Handles authorization requests and callback processing, including state validation and token retrieval. ```python # quickstart-python/routes/auth.py import secrets from urllib.parse import urlencode from flask import request, Blueprint, redirect, make_response from services import SecureStorage, JWTService, EnvironmentVariablesService auth_bp = Blueprint('auth', __name__) @auth_bp.route('/', methods=['GET']) @monday_request_auth # decorator that validates JWT and sets request.session async def authorize(): user_id = str(request.session.get('userId')) back_to_url = request.session.get('backToUrl') # Skip OAuth if already authorized connection = await SecureStorage.get(user_id) if connection and connection.get('monday_token'): return redirect(back_to_url) await SecureStorage.put(user_id, {'back_to_url': back_to_url}) state = secrets.token_urlsafe(16) # CSRF token client_id = await EnvironmentVariablesService.get_environment_variable('MONDAY_OAUTH_CLIENT_ID') oauth_base_path = await EnvironmentVariablesService.get_environment_variable('MONDAY_OAUTH_BASE_PATH') redirect_url = f"{oauth_base_path}?{urlencode({'client_id': client_id, 'state': state})}" response = make_response(redirect(redirect_url)) response.set_cookie('user_id', user_id) response.set_cookie('state', state) return response @auth_bp.route('/monday/callback', methods=['GET']) async def monday_callback(): code = request.args.get('code') user_id = request.cookies.get('user_id') validate_state() # raises GenericBadRequestError on CSRF mismatch monday_token = await JWTService.get_monday_token(code) # monday_token = { "access_token": "eyJ...", "token_type": "Bearer" } back_to_url = (await SecureStorage.get(user_id)).get('back_to_url') await SecureStorage.put(user_id, {'monday_token': monday_token}) return redirect(back_to_url) ``` -------------------------------- ### Initialize and Use SecretsService Source: https://context7.com/mondaycom/monday-code-quickstarts/llms.txt Singleton service for accessing encrypted secrets configured in the monday-code Secrets section. It mirrors the API of EnvironmentVariablesService and is used for sensitive values like client secrets. ```typescript import { SecretsManager } from '@mondaycom/apps-sdk'; export class SecretsService { private static instance: SecretsService; private mondayCodeSecretsManager = new SecretsManager(); static getInstance() { if (!this.instance) this.instance = new SecretsService(); return this.instance; } get(key: string, options = { throwOnUndefined: true }): JsonValue | undefined { return this.mondayCodeSecretsManager.get(key, options); } getKeys(options?: { invalidate?: boolean }): string[] { return this.mondayCodeSecretsManager.getKeys(options); } } // Defined secret keys (set in monday-code dashboard under Secrets): export enum SecretsKeys { MONDAY_SIGNING_SECRET = 'MONDAY_SIGNING_SECRET', MONDAY_OAUTH_CLIENT_SECRET = 'MONDAY_OAUTH_CLIENT_SECRET', } // Usage (inside JWTService): const signingSecret = SecretsService.getInstance().get(SecretsKeys.MONDAY_SIGNING_SECRET) as string; const jwtToken = jwt.sign({ userId: '42' }, signingSecret, { algorithm: 'HS256' }); ``` -------------------------------- ### Generate GraphQL Types Source: https://github.com/mondaycom/monday-code-quickstarts/blob/main/quickstart-node/README.md Generate types for your GraphQL queries, mutations, and fragments by writing them in the `queries.graphql.ts` file and running this command. ```bash npm run fetch:generate ``` -------------------------------- ### Structured Logging with LoggerService Source: https://context7.com/mondaycom/monday-code-quickstarts/llms.txt Utilize LoggerService for structured logging in your monday.com app. Logs can be viewed in the monday.com console using the 'mapps code:logs' command. Supports info, debug, warn, and error levels. ```typescript import { Logger } from '@mondaycom/apps-sdk'; const packageJson = require('../../../package.json'); export class LoggerService { private static instance: LoggerService; private mondayCodeLoggerManager = new Logger(packageJson.name || 'my-app'); static getInstance() { if (!this.instance) this.instance = new LoggerService(); return this.instance; } info(message: string): void { this.mondayCodeLoggerManager.info(message); } debug(message: string): void { this.mondayCodeLoggerManager.debug(message); } warn(message: string): void { this.mondayCodeLoggerManager.warn(message); } error(message: string, error: Error): void { this.mondayCodeLoggerManager.error(message, { error }); } } ``` ```typescript // Usage: const logger = LoggerService.getInstance(); logger.info('Server started'); logger.debug(`Processing queue message: ${JSON.stringify(msg)}`); logger.warn('Queue secret mismatch — ignoring message'); logger.error('Unhandled error in route', err); // View live logs after deployment: // mapps code:logs ``` -------------------------------- ### Publish and Process Queue Messages with QueueService Source: https://context7.com/mondaycom/monday-code-quickstarts/llms.txt Use QueueService to publish messages to the monday.com Worker Queue and handle incoming callbacks. Ensure the secret is validated on the callback endpoint. ```typescript import { Queue } from '@mondaycom/apps-sdk'; import { QueueMethods } from '@shared/queue.consts'; // enum: SEND_EMAIL export class QueueService { private static instance: QueueService; private mondayCodeQueueManager = new Queue(); static getInstance() { if (!this.instance) this.instance = new QueueService(); return this.instance; } // Publish a job — returns a message ID string publishMessage(message: QueueMessage, options?: { topicName: string }): Promise { return this.mondayCodeQueueManager.publishMessage(JSON.stringify(message), options); } // Validates the ?secret= query param on the /mndy-queue callback validateMessageSecret(secret?: string): boolean { return this.mondayCodeQueueManager.validateMessageSecret(secret); } // Route handler: POST /mndy-queue (called by monday-code infrastructure) // message shape: { method: QueueMethods, content: string, userToken: string } async parseQueueMessage(message: QueueMessage): Promise { if (message.method === QueueMethods.SEND_EMAIL) { const mailAddress = (await new StorageService(message.userToken).get('mailAddress'))?.value; if (mailAddress && message.content) { await MailService.sendMail(mailAddress, message.content); } } } } ``` ```typescript // --- Publish from action handler (POST /mail/send) --- await QueueService.getInstance().publishMessage({ method: QueueMethods.SEND_EMAIL, content: `Trigger ran on board ${boardId}`, userToken: mondayAccessToken, }); // → "msg_id_abc123" ``` ```typescript // --- Receive callback at POST /mndy-queue --- mondayQueueRouter.post('/mndy-queue', bodyParser.json(), async (req, res) => { const { secret } = req.query; if (!QueueService.getInstance().validateMessageSecret(secret as string)) { throw new Unauthorized('Not authorized to use monday queue'); } await QueueService.getInstance().parseQueueMessage(req.body); res.status(200).send('Received'); }); ``` -------------------------------- ### JWTService: Sign, Verify, and Exchange OAuth Codes Source: https://context7.com/mondaycom/monday-code-quickstarts/llms.txt Provides functions to create app-signed JWTs, decode/verify monday session tokens, and exchange OAuth authorization codes for monday access tokens. Ensure necessary secrets and environment variables are configured. ```typescript // quickstart-node/src/services/jwt.service.ts import { EnvironmentVariablesService, SecretsService } from '@services/monday-code'; import jwt, { JsonWebTokenError, JwtPayload, TokenExpiredError } from 'jsonwebtoken'; import { InternalServerError, Unauthorized } from 'http-errors'; import axios from 'axios'; import { EnvironmentsKeys, SecretsKeys } from '@shared/monday-auth-consts'; // Sign an arbitrary payload with the app's MONDAY_SIGNING_SECRET const createJWTToken = (payload: object): string => { const signingSecret = SecretsService.getInstance().get(SecretsKeys.MONDAY_SIGNING_SECRET) as string; return jwt.sign(payload, signingSecret, { algorithm: 'HS256' }); }; // Verify (default) or decode (verify=false) a monday-issued session JWT const decodeMondayJWT = (token: string, verify: boolean = true): JwtPayload => { const signingSecret = SecretsService.getInstance().get(SecretsKeys.MONDAY_SIGNING_SECRET) as string; if (!signingSecret) throw new InternalServerError('No signing secret found'); try { return ( verify ? jwt.verify(token, signingSecret, { algorithms: ['HS256'] }) : jwt.decode(token) ) as JwtPayload; } catch (error: any) { if (error instanceof TokenExpiredError) throw new Unauthorized('Token has expired'); if (error instanceof JsonWebTokenError) throw new Unauthorized('Invalid signature, token validation failed'); throw new Unauthorized('Invalid token'); } }; // Exchange OAuth code for a monday access token const getMondayToken = async (code: string): Promise => { const tokenPath = EnvironmentVariablesService.getInstance().get(EnvironmentsKeys.MONDAY_OAUTH_TOKEN_PATH) as string; const clientId = EnvironmentVariablesService.getInstance().get(EnvironmentsKeys.MONDAY_OAUTH_CLIENT_ID); const clientSecret = SecretsService.getInstance().get(SecretsKeys.MONDAY_OAUTH_CLIENT_SECRET); const response = await axios.post(tokenPath, { code, client_id: clientId, client_secret: clientSecret, }); return response.data; // { access_token: "...", token_type: "Bearer" } }; export const JWTService = { createJWTToken, decodeMondayJWT, getMondayToken }; ``` -------------------------------- ### Set Monday API Client Middleware Source: https://context7.com/mondaycom/monday-code-quickstarts/llms.txt This middleware creates an ApiClient instance scoped to the current request context using a short-lived token. It must be applied after authentication middleware. ```typescript // quickstart-node/src/middlewares/monday-api.middleware.ts import { ApiClient, ClientError } from '@mondaydotcomorg/api'; import { executionContext } from '@utils/execution-context.utils'; import { UPDATED_MONDAY_API_VERSION } from '@services/monday.service'; // '2024-10' export const setMondayApiClient = (req: Request, res: Response, next: NextFunction) => { const { shortLivedToken } = req.session; if (!shortLivedToken) throw new Unauthorized('Missing Authorization Token'); // Store the ApiClient in AsyncLocalStorage so MondayService can retrieve it // without passing it through every function call. executionContext.run( { mondayApiClient: new ApiClient({ token: shortLivedToken, apiVersion: UPDATED_MONDAY_API_VERSION }) }, () => next(), ); }; // Monday GraphQL API error handler — place after route handlers on the same router export const mondayApiErrorHandler = (err: any, req: Request, res: Response, next: NextFunction) => { if (err instanceof ClientError) { LoggerService.getInstance().error('Monday Platform API error occurred', err); res.status(HttpStatusCode.BadRequest).send('An error occurred during the request to monday'); } else { next(err); } }; // Router setup: // mondayRouter.use(mondayRequestAuth); // mondayRouter.use(setMondayApiClient); // mondayRouter.post('/me', handler); // mondayRouter.use(mondayApiErrorHandler); // last on this router ``` -------------------------------- ### JWT Authentication Middleware Source: https://context7.com/mondaycom/monday-code-quickstarts/llms.txt Validates incoming monday.com session tokens from headers or query parameters. It decodes the JWT using `MONDAY_SIGNING_SECRET` and populates `req.session` with user and account information. Apply this middleware to routes requiring authentication. ```typescript // quickstart-node/src/middlewares/auth.middleware.ts import { NextFunction, Request, Response } from 'express'; import { JWTService } from '@services/jwt.service'; import { Unauthorized } from 'http-errors'; export const mondayRequestAuth = (req: Request, res: Response, next: NextFunction) => { const token = (req.headers['authorization'] || req.query.token || req.query.sessionToken) as string | undefined; if (!token) { throw new Unauthorized('Missing Authorization Token'); } // Verifies signature using MONDAY_SIGNING_SECRET; throws 401 on expiry or invalid sig const { accountId, userId, backToUrl, shortLivedToken } = JWTService.decodeMondayJWT(token); req.session = { accountId, userId, backToUrl, shortLivedToken }; next(); }; // Usage: apply as route-level middleware // mondayRouter.use(mondayRequestAuth); // curl example (monday sends sessionToken as query param from iframe): // curl -X POST http://localhost:3000/monday/me \ // -H "Content-Type: application/json" \ // -G --data-urlencode "sessionToken=" ``` -------------------------------- ### POST /mail/send Source: https://context7.com/mondaycom/monday-code-quickstarts/llms.txt Publishes a mail sending task to the Worker Queue. This endpoint is designed to be called by monday.com automations or triggers. ```APIDOC ## POST /mail/send ### Description Publishes a mail sending task to the Worker Queue. This endpoint is designed to be called by monday.com automations or triggers. ### Method POST ### Endpoint /mail/send ### Request Body - **payload** (object) - Required - Contains input fields for the mail action. - **inputFields** (object) - Required - Input fields for the mail action. - **boardId** (string) - Optional - The ID of the board associated with the action. ### Request Example ```json { "payload": { "inputFields": { "boardId": "1234567890" } } } ``` ### Response #### Success Response (200) - **Received** (string) - Indicates the request was successfully received and queued. #### Response Example ``` Received ``` ``` -------------------------------- ### Python Worker Queue Route Source: https://context7.com/mondaycom/monday-code-quickstarts/llms.txt Handles POST requests from the monday-code Worker Queue to process dequeued jobs. It parses the incoming message and delegates processing. ```python # quickstart-python/routes/worker_queue.py worker_queue_bp = Blueprint('worker_queue', __name__) @worker_queue_bp.route('/mndy-queue', methods=['POST']) async def handle_queue_message(): """Receives the asynchronous callback from monday-code Worker Queue.""" data = request.get_json() # data = { "method": "send_mail", "content": "...", "user_token": "eyJ..." } await QueueService.parse_queue_message(data) return 'Received', 200 ``` -------------------------------- ### POST /mndy-queue Source: https://context7.com/mondaycom/monday-code-quickstarts/llms.txt Processes dequeued jobs from the Worker Queue. This endpoint is called by the monday-code Worker Queue to handle asynchronous tasks. ```APIDOC ## POST /mndy-queue ### Description Processes dequeued jobs from the Worker Queue. This endpoint is called by the monday-code Worker Queue to handle asynchronous tasks. ### Method POST ### Endpoint /mndy-queue ### Request Body - **data** (object) - Required - The message data dequeued from the worker queue. - **method** (string) - The method to be executed (e.g., "send_mail"). - **content** (string) - The content of the message. - **user_token** (string) - The user's access token. ### Response #### Success Response (200) - **Received** (string) - Indicates the message was successfully processed. #### Response Example ``` Received ``` ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.