Netlify Functions & Static Site: EPDK Proxy & Maps Redirect
December 20, 2025 · View on GitHub
This project demonstrates combining Netlify Serverless Functions with a static HTML frontend served from the same origin. It provides:
- Static Frontend: An
index.htmlfile (generated fromindex.template.htmlduring build) that displays a Google Map and allows testing the API endpoints. - EPDK Station Proxy Function: (
/api/station/:id/sockets) Proxies requests to the EPDK SARJ API (sarjtr.epdk.gov.tr) to retrieve simplified EV charging station socket details (ID, price, availability). - Google Maps Redirect Resolver Function: (
/api/maps/redirect) Resolves Google Maps short links (https://maps.app.goo.gl/...) to their final destination URL.
The frontend and functions are served from the same domain, eliminating the need for complex CORS configuration. Environment variables (like the Google Maps API Key) are injected into the frontend HTML during the Netlify build process.
Features
- Static HTML Frontend:
- Uses a build script (
scripts/build-html.mjs) to inject theGOOGLE_MAPS_API_KEYenvironment variable intoindex.template.html, outputting the finalindex.htmlto thedistdirectory. - Displays a Google Map using the injected API key.
- Provides simple UI elements to test the backend function endpoints.
- Uses a build script (
- Station Socket Proxy (
/api/station/:id/sockets):- Fetches detailed station data from the EPDK API based on a station ID (extracted manually from the path).
- Calculates the required timestamp for the API request.
- Parses the response and returns a simplified array of sockets with their ID, current price, and calculated availability status (
FREE,IN_USE,UNKNOWN). - Handles fetch timeouts and upstream API errors gracefully.
- Google Maps Redirect Resolver (
/api/maps/redirect):- Accepts a Google Maps short URL (
maps.app.goo.gl) via aurlquery parameter. - Follows HTTP redirects to find the final destination URL.
- Handles fetch timeouts and network errors.
- Accepts a Google Maps short URL (
- Same-Origin: Frontend and backend functions served from the same Netlify domain, simplifying requests.
- Environment Variable Management: Uses Netlify build environment variables for configuration (API keys, etc.), keeping secrets out of the repository. Uses
.envfile anddotenvpackage for local development builds.
Technology Stack
- Node.js (v20 required for E2E tests, v18+ for other features)
- Netlify (Platform for Hosting, Build, Functions)
- Netlify Functions
- Static HTML, CSS, TypeScript (compiled to JavaScript)
- TypeScript (for type-safe JavaScript)
- Vitest (for unit testing)
- Playwright (for UI/E2E testing)
- ESLint (for code linting and quality checks)
- Prettier (for code formatting)
- Knip (for unused code detection)
- jscpd (for duplicate code detection)
- eslint-plugin-security (for security vulnerability detection)
dotenv(for local build environment variables)- Native
fetchAPI (in Node.js functions)
Project Structure
.
├── netlify.toml # Netlify config (build, redirects, functions)
├── package.json # Dependencies and build scripts
├── index.template.html # Source HTML template with placeholders
├── .prettierrc # Prettier configuration
├── .prettierignore # Files ignored by Prettier
├── eslint.config.mjs # ESLint configuration
├── knip.json # Knip configuration for unused code detection
├── .jscpd.json # jscpd configuration for duplicate code detection
├── vitest.config.ts # Vitest configuration for unit tests
├── playwright.config.cjs # Playwright configuration for E2E tests
├── scripts/
│ └── build-html.mjs # Node.js script to build index.html from template
├── css/ # Example directory for static CSS
│ └── style.css
├── js/ # TypeScript source files for client-side code
│ ├── *.ts # TypeScript source files
│ ├── *.test.ts # Unit test files
│ ├── *.js # Compiled JavaScript files (generated)
│ └── global.d.ts # Global type declarations
├── test/
│ ├── unit/ # Unit tests directory
│ └── ui/ # E2E/UI tests directory (Playwright)
│ └── app.spec.ts # Main UI test suite
├── tsconfig.json # TypeScript configuration
├── netlify/
│ └── functions/ # Directory for Netlify Function source code
│ ├── station-sockets.js
│ └── maps-redirect.js
├── utils/ # Shared utility code for functions
│ ├── config.js # Exports getConfig() function (reads env vars)
│ └── helpers.js # Helper functions (formatting, etc.)
├── .env # Example environment file (for contributors)
└── .gitignore # Specifies intentionally untracked files
Setup and Local Development
Prerequisites:
- Node.js v20 (required for E2E tests, see
.nvmrc)- Use
nvm use 20to switch to the correct version
- Use
- npm or yarn
- Netlify CLI (installed as dev dependency, no global install needed)
Steps:
-
Clone the repository:
git clone <your-repository-url> cd <your-repository-directory> -
Install dependencies:
npm install # or yarn install -
Set up local environment variables:
- Create a
.envfile in the project root by copying.env.exampleor creating it manually. - Crucially, add
.envto your.gitignorefile! - Define the necessary variables for local testing, matching the names expected by
scripts/build-html.mjsandutils/config.js.
# .env - LOCAL DEVELOPMENT ONLY! Add to .gitignore! # Required by build script for Google Maps in index.html GOOGLE_MAPS_API_KEY="YOUR_GOOGLE_MAPS_API_KEY_FOR_LOCALHOST" # Optional: Set to 0 ONLY if the EPDK API has TLS issues (read by utils/config.js) # NODE_TLS_REJECT_UNAUTHORIZED=0 # Optional: Override function config defaults if needed locally # BASE_URL=http://localhost:9999/mock-epdk # FETCH_TIMEOUT=7000 # MAPS_REDIRECT_TIMEOUT=12000 - Create a
-
Run the local build: This step compiles TypeScript to JavaScript and generates the
dist/index.htmlfile with the local API key injected. You need to run this at least once beforenetlify devor whenever you change the template/assets or TypeScript files.npm run buildThe build process:
- Compiles TypeScript files from
js/*.tstojs/*.js - Processes the HTML template and copies assets to
dist/
- Compiles TypeScript files from
-
Run the development server:
netlify devserves static files from thedistdirectory and runs your functions locally.netlify dev- Open your browser to
http://localhost:8888(or the port indicated by the CLI). You should see yourindex.htmlwith the map (if the API key is correct and configured for localhost). - The function endpoints are also available, e.g.,
http://localhost:8888/api/station/123/sockets. Requests from the servedindex.htmlto/api/...will work due to same-origin.
- Open your browser to
Code Quality
This project uses ESLint and Prettier to maintain code quality and consistent formatting across the codebase.
Available Scripts:
npm run lint- Run ESLint to check for code quality issuesnpm run lintnpm run lint:fix- Automatically fix ESLint issues where possiblenpm run lint:fixnpm run format- Format all TypeScript files with Prettiernpm run formatnpm run format:check- Check if files are formatted correctly (without modifying them)npm run format:checknpm run check- Run all quality checks (lint + format + type check)npm run check
Configuration Files:
.prettierrc- Prettier configuration (code formatting rules).prettierignore- Files and directories ignored by Prettiereslint.config.mjs- ESLint configuration (linting rules)tsconfig.json- TypeScript compiler configuration
Recommended Workflow:
Before committing code, run npm run check to ensure all quality checks pass. You can also run npm run lint:fix && npm run format to automatically fix most issues.
Unused Code Detection
This project uses automated tools to detect unused code, exports, and dependencies.
Available Scripts:
npm run knip- Find unused files, exports, and dependencies (comprehensive)npm run knipnpm run knip:production- Check for unused production dependencies onlynpm run knip:productionnpm run ts-prune- Find unused TypeScript exports (lightweight alternative)npm run ts-prunenpm run find:unused- Alias fornpm run knipnpm run find:unused
Configuration Files:
knip.json- Knip configuration (defines entry points, ignored files, and exceptions)
What These Tools Find:
- Unused exported functions, classes, and variables
- Unused files (not imported anywhere)
- Unused npm dependencies
- Dead code and unreachable code
Recommended Usage:
Run npm run knip periodically (e.g., before major releases) to identify and remove unused code. This helps:
- Reduce bundle size
- Improve build performance
- Keep the codebase maintainable
- Identify refactoring opportunities
Duplicate Code Detection
This project uses jscpd (Copy/Paste Detector) to identify duplicated code blocks that could be refactored.
Available Scripts:
npm run jscpd- Find duplicate code blocksnpm run jscpdnpm run find:duplicates- Alias fornpm run jscpdnpm run find:duplicates
Configuration Files:
.jscpd.json- jscpd configuration (thresholds, output format, ignored files)
What This Tool Finds:
- Duplicated code blocks (5+ lines by default)
- Copy-pasted code that could be refactored into functions
- Similar code patterns across files
- Generates HTML report in
reports/jscpd/html/
Current Status:
The codebase currently has 0.09% duplication (1 small clone), which is excellent! The threshold is set to 1%, so builds will fail if duplication exceeds that.
Recommended Usage:
Run npm run jscpd before major releases to identify refactoring opportunities. Consider extracting duplicated code into shared utility functions when:
- The duplicate is used in multiple files
- The logic is complex and likely to change
- It improves code readability
Complexity and Security Analysis
This project includes automated complexity analysis and security scanning to maintain code quality and identify potential vulnerabilities.
Available Scripts:
npm run audit- Check for security vulnerabilities in dependenciesnpm run auditnpm run audit:fix- Automatically fix security vulnerabilitiesnpm run audit:fixnpm run security- Run full security check (audit + lint with security rules)npm run security
Complexity Rules (ESLint):
The following complexity metrics are enforced as warnings:
- Cyclomatic Complexity: Maximum 15 per function
- Max Function Parameters: 5 parameters
- Max Function Length: 150 lines (excluding blanks/comments)
- Max Nesting Depth: 4 levels
- Max Statements: 30 per function
- Max Nested Callbacks: 3 levels
Security Rules (eslint-plugin-security):
The following security patterns are detected:
- Unsafe regular expressions (DoS vulnerability)
eval()usage with expressions- Object injection vulnerabilities
- Timing attacks in comparisons
- Non-cryptographic random number generation
- Buffer operations without assertions
Current Status:
- Dependencies: 0 vulnerabilities ✅
- Complexity: 3 functions exceed thresholds (warnings)
- Security: 2 potential object injection warnings (false positives in array access)
Recommended Usage:
- Run
npm run auditregularly (weekly or before releases) - Address complexity warnings when refactoring
- Review security warnings carefully - some may be false positives
- Use
npm audit fixto auto-update vulnerable dependencies
Testing
This project uses Vitest for fast, modern unit testing of TypeScript utility functions.
Available Scripts:
npm test- Run all tests oncenpm testnpm run test:watch- Run tests in watch mode (re-run on file changes)npm run test:watchnpm run test:ui- Open Vitest UI for interactive test explorationnpm run test:uinpm run test:coverage- Generate test coverage reportnpm run test:coverage
Configuration Files:
vitest.config.ts- Vitest configuration (test environment, coverage settings)
Test Structure:
All test files follow the pattern *.test.ts and are located alongside their corresponding source files in the js/ directory:
js/validation-utils.test.ts- Tests for number validation functionsjs/storage-utils.test.ts- Tests for localStorage utilitiesjs/async-utils.test.ts- Tests for async/retry utilities with mockingjs/geo-utils.test.ts- Tests for geographic utilities
Current Status:
- Test Files: 4
- Total Tests: 40
- Status: All passing ✅
Coverage Reporting:
Coverage reports are generated using V8 provider and output in multiple formats:
- Text output to console
- HTML report in
coverage/directory - LCOV format for CI integration
Recommended Usage:
- Run
npm testbefore committing changes - Use
npm run test:watchduring development for instant feedback - Review coverage reports periodically to identify untested code paths
- Add tests when fixing bugs to prevent regression
UI Testing (E2E)
This project uses Playwright for end-to-end browser testing to ensure the UI works correctly.
Prerequisites:
- Node.js v20 (specified in
.nvmrc) - Run
nvm use 20before running E2E tests
Available Scripts:
npm run test:e2e- Run all UI tests in headless modenpm run test:e2enpm run test:e2e:ui- Open Playwright UI mode (recommended for development)npm run test:e2e:uinpm run test:e2e:headed- Run tests with visible browsernpm run test:e2e:headednpm run test:e2e:debug- Run tests in debug mode with inspectornpm run test:e2e:debugnpm run test:e2e:report- View test results reportnpm run test:e2e:report
Configuration Files:
playwright.config.cjs- Playwright configuration (browser settings, test directories)
What These Tests Cover:
Important Notes:
- E2E tests automatically start the dev server (
netlify dev) - Make sure to run
npm run buildfirst if you've made changes to TypeScript or templates - Tests run in Chromium by default
- Screenshots are captured on test failures in
test-results/ - HTML reports are generated in
playwright-report/ - Some tests access
window.appStateand other global variables - adjust based on your implementation - Tests that interact with Google Maps API may need API keys to work fully
Adjusting Tests to Your Implementation:
Some tests make assumptions about implementation details and may need adjustment:
-
Global State Variables: Tests access
window.appState,window.markers, etc.- Update tests to match your actual global variable names
- Or expose these from your app for testing purposes
-
Filter Implementation: Tests check if
window.appState.visibleStationschanges- Adjust to match how your app tracks filtered stations
- Ensure filters actually modify the visible station list
-
Event Handlers: Tests call handlers like
window.handleMapClick()- Expose necessary handlers globally for testing
- Or use DOM events to trigger functionality
-
Geolocation & API Mocking: Some tests require API responses
- Consider mocking external APIs for consistent testing
- Or use Playwright's route interception features
Recommended Usage:
- Run
npm run test:e2ebefore refactoring to ensure UI behavior is preserved - Use
npm run test:e2e:uiduring development for visual feedback and debugging - Add new tests when implementing new UI features
- Update failing tests to match your actual implementation
- Run tests in CI/CD pipeline to catch regressions
- Use
npm run test:e2e:reportto view detailed test results with screenshots
Configuration
Configuration relies on environment variables set either locally via .env (for npm run build) or, crucially, via the Netlify UI for deployments.
Key Environment Variables (Set in Netlify UI):
GOOGLE_MAPS_API_KEY(Required): Your client-side Google Maps API Key. Set this in the Netlify UI, scoped appropriately for each deployment context (Production,mainbranch, etc.). Remember to configure HTTP Referer restrictions in Google Cloud Console for this key.NODE_TLS_REJECT_UNAUTHORIZED(Optional): Set to0only if absolutely necessary due to upstream API certificate issues. Affects functions runtime. Set in Netlify UI if needed (Scope: All Scopes or specific contexts).
Optional Environment Variables (Set in Netlify UI to override defaults in utils/config.js):
BASE_URL: Override the default EPDK API base URL for functions.FETCH_TIMEOUT: Override the default timeout (ms) for EPDK API calls for functions.MAPS_REDIRECT_TIMEOUT: Override the default timeout (ms) for Google Maps redirects for functions.
Deployment
- Connect Git Repository: Link your Git repository to a new or existing site on Netlify.
- Configure Build Settings: Ensure Netlify is configured to:
- Use the correct Production branch (e.g.,
prod). - Track other branches for deploys (e.g.,
mainfor dev). - Use the Build command:
npm run build. - Use the Publish directory:
dist. - Use the correct Functions directory:
netlify/functions. (Often auto-detected fromnetlify.toml).
- Use the correct Production branch (e.g.,
- Set Environment Variables in Netlify UI: Go to
Site settings > Build & deploy > Environment. AddGOOGLE_MAPS_API_KEYand any other necessary variables (likeNODE_TLS_REJECT_UNAUTHORIZED). Crucially, scope the variables correctly to apply the right values to the right deployment contexts (e.g., Production context gets prod API key,mainbranch context gets dev API key). - Trigger Deploy: Push changes to your connected Git branches (
main,prod). Netlify will:- Inject the scoped environment variables for that branch/context.
- Run
npm run build. - Deploy the contents of the
distdirectory. - Deploy the functions from
netlify/functions.
API Endpoints / Usage
(These are called from the client-side JavaScript js/script.js)
1. Get Station Sockets
- Path:
/api/station/:id/sockets - Method:
GET - Path Parameters:
:id(Required): The numerical ID of the charging station (extracted from path).
- Success Response (200 OK): Array of simplified socket objects.
[ { "id": 501, "price": 7.5, "availability": "FREE" }, /* ... */ ] - Error Responses:
400,405,500,502,504.
2. Resolve Google Maps Redirect
- Path:
/api/maps/redirect - Method:
GET - Query Parameters:
url(Required): The encoded Google Maps short URL (maps.app.goo.gl).
- Success Response (200 OK):
{ "redirectedUrl": "https://www.google.com/maps/place/..." } - Error Responses:
400,405,500,502,504.
Security Considerations
- Google Maps API Key: The client-side key injected into the HTML is inherently public. Rely on HTTP Referer restrictions configured in your Google Cloud Console as the primary security mechanism for this key.
- TLS Verification: Disabling TLS verification (
NODE_TLS_REJECT_UNAUTHORIZED=0) for function outbound requests is a security risk. Only use if the target API has certificate issues you cannot control. - Function Access: Since CORS is not needed/used, the function endpoints are technically callable by any client that knows the URL. Access isn't restricted beyond standard Netlify infrastructure protections. If sensitive operations were involved, authentication/authorization would be required.
License
MIT