What youβll learn: JavaScript from the very beginning β variables, functions, classes, async/await, error handling β everything needed to understand EPICBackend and EPICFrontend code.
Assumes: You know basic programming concepts (what a loop is, what a condition is). Nothing more.
Section 1 β What Is JavaScript?
JavaScript started as a language for web browsers (making websites interactive). Today it also runs on servers using Node.js.
In EPIC:
- EPICBackend = JavaScript running on servers (Node.js inside AWS Lambda)
- EPICFrontend = JavaScript running in the browser (React)
Both use the same language β JavaScript β but in different environments.
Your Browser ββββ loads ββββ EPICFrontend (JavaScript/React)
β makes HTTP calls
AWS Lambda ββββ runs ββββ EPICBackend (JavaScript/Node.js)
Section 2 β Variables: Storing Data
In JavaScript, you store data in variables using const, let, or var:
// const = value never changes (use this most of the time)
const fleetId = "RIPE-NA";
const maxHosts = 500;
// let = value can change
let currentStatus = "Running";
currentStatus = "Completed"; // OK, let allows this
// var = old way, avoid it in modern code
In EPIC code youβll see:
// From HOTW.js
const hotwOperations = new HOTWOperations(); // const: this object won't be reassigned
let body = {}; // let: body gets assigned later in try/catch
let eventId = ''; // let: will be filled from request
Data types in JavaScript:
// String (text)
const eventId = "PrimeDay2024";
// Number (no difference between int and double like Java)
const peakTpm = 50000;
const ctFactor = 1.25;
// Boolean (true/false)
const isEmergent = true;
// null (intentionally empty)
const ticket = null;
// undefined (not set yet)
let result; // result is undefined
// Object (key-value pairs, like a Map or a class instance)
const fleet = {
FleetId: "RIPE-NA",
PeakTPM: 50000,
Region: "us-east-1"
};
// Array (list of items)
const asgList = ["RIPE/NA/PROD/1a", "RIPE/NA/PROD/1b", "RIPE/NA/PROD/1c"];
Section 3 β Functions: Reusable Code Blocks
// Regular function
function addNumbers(a, b) {
return a + b;
}
addNumbers(10, 20); // returns 30
// Arrow function (shorter syntax, very common in EPIC)
const addNumbers = (a, b) => a + b;
addNumbers(10, 20); // same result: 30
// Arrow function with multiple lines
const calculateBauTpm = (peakTpm, ctFactor, bufferFactor) => {
const result = peakTpm / (ctFactor * bufferFactor);
return result;
};
Why arrow functions everywhere? Theyβre shorter and handle this keyword differently (important in callbacks β more on that later).
Section 4 β Classes: Organizing Code
JavaScript has classes similar to Java. In EPICβs backend, almost every handler is a class:
// From EPICBackend β HOTWOperations.js
class HOTWOperations {
constructor() {
// constructor runs when you create a new HOTWOperations()
this.db = new AuroraDB(); // 'this' means "this object"
}
async createOrUpdateHotwDashBoardTable(tableName, entries) {
// method of the class
const query = `INSERT INTO ${tableName} ...`;
return await this.db.insertSingleQueryPromise(query, entries);
}
}
// Using the class:
const hotwOps = new HOTWOperations();
hotwOps.createOrUpdateHotwDashBoardTable("hotw_dashboard", data);
Static methods (donβt need to create an object):
// From Fleet.js backend
class Fleet {
static async createFleet(event) {
// static = call it as Fleet.createFleet(event)
// no need for new Fleet()
}
}
// Used as:
Fleet.createFleet(event); // NOT: new Fleet().createFleet(event)
The difference:
- Regular method:
const fleet = new Fleet(); fleet.doSomething(); - Static method:
Fleet.doSomething();(directly on the class, no new)
Section 5 β The require and module.exports System
In Node.js, code is organized into files. Each file is a βmoduleβ. To use code from another file:
// In HOTWOperations.js β this is EXPORTING (making code available)
class HOTWOperations {
// ...
}
module.exports = HOTWOperations; // this line makes it available to others
// In HOTW.js β this is IMPORTING (using code from another file)
const HOTWOperations = require('../operations/HOTWOperations');
// Now HOTWOperations is available in this file
Real EPIC example from HOTW.js:
// Top of HOTW.js β all imports
const HOTWConstants = require('../common/HOTWConstants');
const FleetOperations = require('../operations/FleetOperations');
const ServiceOperations = require('../operations/ServiceOperations');
const OtherConstants = require('../common/OtherConstants');
const Util = require('../common/Util');
// This means this file uses code from all these other files
Path conventions:
./filename= same folder../filename= one folder up../../filename= two folders up
Section 6 β Objects and Destructuring
Objects in JavaScript are like JSON. This is the most common data format in EPIC:
// Creating an object
const fleet = {
FleetId: "RIPE-NA",
ServiceId: "RIPE",
PeakTPM: 50000,
Region: "us-east-1"
};
// Accessing properties β TWO WAYS:
fleet.FleetId; // dot notation: "RIPE-NA"
fleet["FleetId"]; // bracket notation: "RIPE-NA" (same result)
// Adding a new property
fleet.EventId = "PD2024";
// Spreading (copying all properties) β common in EPIC
const original = { FleetId: "RIPE-NA", PeakTPM: 50000 };
const copy = { ...original }; // copy has same properties
const withExtra = { ...original, Region: "us-east-1" }; // copy + new property
In HOTW.js you see this pattern:
body = JSON.parse(event[OtherConstants.BODY]); // parse JSON string β object
const entryToInsert = { ...body }; // copy the body object
entryToInsert.EventIndexId = eventIndexId; // add one more property
Destructuring β extract multiple properties at once:
const fleet = { FleetId: "RIPE-NA", Region: "us-east-1", PeakTPM: 50000 };
// Instead of:
const fleetId = fleet.FleetId;
const region = fleet.Region;
// Destructuring does both in one line:
const { FleetId, Region } = fleet;
Section 7 β Arrays and Their Methods
Arrays (lists) are everywhere in EPIC:
const services = ["RIPE", "FORTRESS", "PayStation"];
const fleets = [
{ FleetId: "RIPE-NA", PeakTPM: 50000 },
{ FleetId: "RIPE-EU", PeakTPM: 30000 }
];
// Loop over array
services.forEach(service => {
console.log(service); // prints each service
});
// Transform array (map) β returns new array
const serviceIds = fleets.map(fleet => fleet.FleetId);
// serviceIds = ["RIPE-NA", "RIPE-EU"]
// Filter array β keep only matching items
const naFleets = fleets.filter(fleet => fleet.FleetId.includes("NA"));
// naFleets = [{ FleetId: "RIPE-NA", PeakTPM: 50000 }]
// Find one item
const ripeFleet = fleets.find(fleet => fleet.FleetId === "RIPE-NA");
// ripeFleet = { FleetId: "RIPE-NA", PeakTPM: 50000 }
// Check if any match (returns true/false)
const hasNAFleet = fleets.some(fleet => fleet.FleetId.includes("NA"));
// Reduce β combine all items into one value
const totalTpm = fleets.reduce((sum, fleet) => sum + fleet.PeakTPM, 0);
// totalTpm = 80000
Real EPIC example from HOTW.js:
// Building aggregateFleetIds from all services
let aggregateFleetIds = [];
for (let service of allRegisteredServices) {
aggregateFleetIds = aggregateFleetIds.concat(
serviceOperations.getFormattedFleetsBasedOnRegionList(
service[SERVICE_CONSTANTS.FLEET], regionList
)
);
}
Section 8 β Promises and async/await
This is the MOST IMPORTANT concept for EPIC backend code. All database and API calls are asynchronous β they take time to complete.
The Problem Without async/await:
// This would FAIL β it doesn't wait for the database!
const result = database.query("SELECT * FROM fleets"); // starts query
console.log(result); // runs IMMEDIATELY, before query finishes β result is undefined!
The Solution β async/await:
// This WORKS correctly
const result = await database.query("SELECT * FROM fleets"); // WAITS for query to finish
console.log(result); // runs AFTER query is done β result has real data
Rules:
- Use
awaitbefore any call that takes time (DB queries, HTTP calls, file reads) - Any function that uses
awaitMUST be markedasync
Full EPIC example:
// From HOTW.js
static async createOrUpdateHotwRunDetails(event) {
// β must be async because we use await inside
const eventOperations = new EventOperations();
try {
body = JSON.parse(event[OtherConstants.BODY]);
eventId = body[EventConstants.EVENT_ID];
} catch (err) {
return Util.handleErr(400, `Error parsing`, err);
}
try {
let eventIndexId = await eventOperations.getEventIndex(eventId);
// β WAIT for this DB call to finish before continuing
const result = await hotwOperations.updateRunTable(tableName, entries, eventIndexId);
// β WAIT for this insert to finish
return Util.handleResponse(200, JSON.stringify(result.insertId));
} catch (err) {
return Util.handleErr(503, `Error inserting`, err);
}
}
Why try/catch with async? If an await fails (network error, DB down), it throws an error. catch catches it.
Section 9 β Template Literals (String Interpolation)
Much better than string concatenation:
const fleetId = "RIPE-NA";
const eventId = "PD2024";
const runId = 5;
// Old way (ugly)
const msg1 = "Fleet " + fleetId + " for event " + eventId + " run " + runId;
// Template literal (modern, used everywhere in EPIC)
const msg2 = `Fleet ${fleetId} for event ${eventId} run ${runId}`;
// result: "Fleet RIPE-NA for event PD2024 run 5"
// Multiline
const query = `
INSERT INTO hotwdashboard
(FleetId, EventId)
VALUES (${fleetId}, ${eventId})
`;
Real EPIC example:
// From EventPlan.js
return Util.handleResponse(
400,
`Invalid Request. Fleet: ${fleetId} for Event: ${eventId} doesn't exist.`
);
Section 10 β Error Handling (try/catch/finally)
try {
// Code that might fail
const result = await database.query("...");
return { success: true, data: result };
} catch (err) {
// Runs if anything in try{} throws an error
console.log("Error:", err);
return { success: false, error: err.message };
} finally {
// ALWAYS runs, whether error or not
// Used for cleanup
await database.closeConnection();
}
EPIC pattern β youβll see this exact structure everywhere:
static async createFleet(event) {
// Step 1: Parse input (might fail if body is malformed)
try {
body = JSON.parse(event[OtherConstants.BODY]);
fleetId = body[FleetConstants.FLEET_ID];
} catch (err) {
return Util.handleErr(400, `Error parsing request`, err);
}
// Step 2: Database operations (might fail if DB is down)
try {
await fleetOperations.createFleet(fleetId, body);
return Util.handleResponse(200, "Success");
} catch (err) {
return Util.handleErr(503, `DB error for fleet: ${fleetId}`, err);
}
}
Why two separate try/catch blocks? Because different errors need different response codes:
- Parsing error β 400 (client sent bad data)
- DB error β 503 (server problem)
Section 11 β JSON: The Data Format of EPIC
JSON (JavaScript Object Notation) is how data moves between frontend β backend β database:
// JavaScript object
const fleet = { FleetId: "RIPE-NA", PeakTPM: 50000 };
// Convert object β JSON string (for sending over network)
const jsonString = JSON.stringify(fleet);
// Result: '{"FleetId":"RIPE-NA","PeakTPM":50000}'
// Convert JSON string β object (for receiving from network)
const parsed = JSON.parse(jsonString);
// Result: { FleetId: "RIPE-NA", PeakTPM: 50000 }
In EPIC Lambda handlers:
// Incoming request has body as JSON string
body = JSON.parse(event[OtherConstants.BODY]); // string β object, now usable
// Sending response with JSON
return Util.handleResponse(200, JSON.stringify(result)); // object β string, sendable
Section 12 β Maps and Sets
// Map β like a dictionary with any key type
const batchEventPlanMap = new Map();
batchEventPlanMap.set("RIPE-NA", { status: "completed" });
batchEventPlanMap.has("RIPE-NA"); // true
batchEventPlanMap.get("RIPE-NA"); // { status: "completed" }
// Iterating a Map
for (let [key, value] of batchEventPlanMap) {
console.log(key, value);
}
// Set β list of UNIQUE values
const processedFleets = new Set();
processedFleets.add("RIPE-NA");
processedFleets.add("RIPE-NA"); // duplicate β ignored!
processedFleets.size; // 1 (not 2)
processedFleets.has("RIPE-NA"); // true
Real EPIC example from HOTW.js:
// Create a hashmap to store batch event plan response by FleetId
const batchEventPlanMap = new Map();
batchEventPlanResponse.forEach(batchEventPlan => {
const fleetId = batchEventPlan.FleetId;
batchEventPlanMap.set(fleetId, batchEventPlan); // key: fleetId, value: plan data
});
Section 13 β Modules Pattern in EPIC
Every EPIC backend file follows the same pattern:
File structure:
1. require() imports at top
2. class definition
3. static async methods
4. module.exports at bottom
Template:
// 1. IMPORTS
const SomeOtherClass = require('../path/to/SomeOtherClass');
const Constants = require('../common/Constants');
// 2. CLASS DEFINITION
class MyHandler {
// 3. METHOD
static async myMethod(event) {
// parse input
// validate
// call operations
// return response
}
}
// 4. EXPORT
module.exports = MyHandler;
Section 14 β Common JavaScript Patterns in EPIC
Pattern 1: Validate required fields
// Util.validateKeys checks if all required fields exist in the body
if (!Util.validateKeys(body, [SERVICE_CONSTANTS.SERVICE_ID, FleetConstants.FLEET_ID])) {
throw TypeError('Required fields are not present in body');
}
Pattern 2: Null/undefined guard
// Use || to provide a default value if something is null/undefined
const alias = queryParameters && queryParameters.alias
? queryParameters.alias
: '';
// If queryParameters is null or doesn't have alias β use empty string ''
Pattern 3: Chaining array methods
const finalList = services
.filter(s => s.status === 'active') // keep only active
.map(s => s.serviceId) // extract just IDs
.sort(); // alphabetically sort
Pattern 4: Spread to merge objects
const body = { FleetId: "RIPE-NA", PeakTPM: 50000 };
const entryToInsert = { ...body }; // copy
entryToInsert.EventIndexId = eventIndexId; // add new field
// entryToInsert = { FleetId: "RIPE-NA", PeakTPM: 50000, EventIndexId: 42 }
Pattern 5: async/await with database transaction
const auroraMysqlClient = new AuroraMysqlClient();
try {
await auroraMysqlClient.startTransaction(); // begin transaction
await hotwOps.insertRecord(data); // insert
await hotwOps.updateAnotherRecord(data); // update
await auroraMysqlClient.commitTransaction(); // commit (save both)
} catch (err) {
await auroraMysqlClient.rollbackTransaction(); // undo BOTH if either failed
}
Section 15 β Quick Reference Card
| Concept | Syntax | Example |
|---|---|---|
| Variable | const x = 5 |
const eventId = "PD2024" |
| Function | const f = (a) => a + 1 |
const double = x => x * 2 |
| Async func | async (a) => await db.get(a) |
See handlers |
| Class | class MyClass { method() {} } |
class HOTW { static async get() {} } |
| Import | const X = require('./X') |
const Util = require('../common/Util') |
| Export | module.exports = MyClass |
Last line of every file |
| Try/catch | try { await op() } catch(e) {} |
See all handlers |
| Template literal | `Hello ${name}` |
`Fleet: ${fleetId}` |
| Spread | { ...obj, newKey: val } |
{ ...body, EventId: id } |
| Array map | arr.map(x => x.id) |
services.map(s => s.ServiceId) |
| Array filter | arr.filter(x => x.active) |
fleets.filter(f => f.region === 'NA') |
| Null guard | x && x.prop ? x.prop : '' |
alias ? alias : '' |