Build an AI‑Powered Personal Carbon Footprint Tracker with React, GraphQL, and OpenAI

Mahmut Sarıkaya 6 min read 3 Views 0
Build an AI‑Powered Personal Carbon Footprint Tracker with React, GraphQL, and OpenAI

Why tracking your carbon footprint matters

Did you know that the average household in the United States emits roughly 7.5 metric tons of CO2 each year, according to the EPA? Turning that number into actionable daily habits is the missing link between awareness and impact. A personal sustainability app that quantifies travel, energy use, and diet can turn vague concerns into concrete data, empowering users to cut emissions by up to 15% within six months.

Building such an app with modern web tools not only showcases cutting‑edge development skills but also creates a reusable platform for other climate‑focused projects. The combination of React for the UI, a GraphQL API for flexible data exchange, and OpenAI embeddings for intelligent suggestions forms a powerful AI carbon tracker.

System requirements and tech stack overview

The prototype runs on any recent laptop or cloud VM with Node.js 18+, npm 9+, and a PostgreSQL 14 database. The front‑end relies on React 18, Vite for fast bundling, and Tailwind CSS for responsive design. The back‑end uses Apollo Server, TypeScript, and Prisma as an ORM. OpenAI’s embedding endpoint (text‑embedding‑ada‑002) supplies semantic similarity scores that power recommendation logic.

Because the stack is fully JavaScript‑centric, developers can share code between client and server, keep type safety, and iterate quickly. All components are open source, and the only paid service is the OpenAI API, which costs less than $0.0004 per 1,000 tokens for embeddings.

Setting up the GraphQL API with Node and Apollo

First, initialize a new Node project and install the required packages:

npm init -y && npm i apollo-server graphql prisma @prisma/client dotenv

Create a .env file with your PostgreSQL connection string and OpenAI key. Then define a simple Prisma schema that stores user entries and calculated emissions:

datasource db { provider = "postgresql" url = env("DATABASE_URL") } generator client { provider = "prisma-client-js" } model EmissionEntry { id Int @id @default(autoincrement()) userId String activity String amount Float unit String carbonKg Float createdAt DateTime @default(now()) }

Run npx prisma migrate dev --name init to create the database tables. The GraphQL type definitions mirror the model:

const typeDefs = ` type EmissionEntry { id: ID! userId: String! activity: String! amount: Float! unit: String! carbonKg: Float! createdAt: String! } type Query { entries(userId: String!): [EmissionEntry!]! } type Mutation { addEntry(userId: String!, activity: String!, amount: Float!, unit: String!): EmissionEntry! } `;

The resolver for addEntry calls a helper that converts raw activity data into CO2 using a static factor table, then stores the result.

const resolvers = { Query: { entries: (_, {userId}, ctx) => ctx.prisma.emissionEntry.findMany({where:{userId}}) }, Mutation: { addEntry: async (_, args, ctx) => { const carbonKg = await calculateCarbon(args.activity, args.amount, args.unit); return ctx.prisma.emissionEntry.create({data:{...args, carbonKg}}); } } };

Wrap everything with Apollo Server and expose the endpoint at /graphql. This GraphQL API becomes the single source of truth for the React client.

Creating the React front‑end

Bootstrap the UI with Vite:

npm create vite@latest ai-carbon-tracker --template react && cd ai-carbon-tracker && npm i @apollo/client graphql tailwindcss postcss autoprefixer && npx tailwindcss init -p

Configure Tailwind in tailwind.config.js and add the CSS import to src/index.css. Then set up Apollo Client to point at the back‑end:

import { ApolloClient, InMemoryCache, ApolloProvider } from "@apollo/client"; const client = new ApolloClient({ uri: "http://localhost:4000/graphql", cache: new InMemoryCache() }); const App = () => ( <ApolloProvider client={client}> <Main/> </ApolloProvider> ); export default App;

The main component renders a form where users select an activity (e.g., "Driving", "Home heating", "Beef consumption"), enter amount and unit, and submit. On submit, the mutation addEntry is called, and the UI updates the list of entries with real‑time carbon totals.

const ADD_ENTRY = gql` mutation AddEntry($userId: String!, $activity: String!, $amount: Float!, $unit: String!) { addEntry(userId:$userId, activity:$activity, amount:$amount, unit:$unit) { id carbonKg createdAt } } `; // Inside component const [addEntry] = useMutation(ADD_ENTRY); const handleSubmit = async e => { e.preventDefault(); await addEntry({ variables:{userId:"user-123", activity, amount:parseFloat(amount), unit } }); };

Because GraphQL returns only the fields you request, the front‑end stays lightweight even as you add new metrics like water usage or waste generation.

Integrating OpenAI embeddings for smart suggestions

OpenAI embeddings turn free‑text activity descriptions into vectors that can be compared against a curated library of high‑impact actions. Store the library in a separate PostgreSQL table ActionLibrary(id, description, embedding, carbonFactor). When a user types a custom activity, the back‑end fetches its embedding, runs a cosine similarity query, and returns the top three matching actions.

import { Configuration, OpenAIApi } from "openai"; const openai = new OpenAIApi(new Configuration({ apiKey: process.env.OPENAI_API_KEY })); async function getEmbedding(text){ const resp = await openai.createEmbedding({ model:"text-embedding-ada-002", input:text }); return resp.data.data[0].embedding; } async function findSimilarActivities(userText){ const userVec = await getEmbedding(userText); const candidates = await prisma.actionLibrary.findMany(); let best = []; for (const c of candidates){ const score = cosineSimilarity(userVec, c.embedding); if (score > 0.78) best.push({id:c.id, description:c.description, score}); } return best.sort((a,b)=>b.score-a.score).slice(0,3); }

The similarity threshold (0.78 in the example) balances relevance and diversity. The front‑end can display these suggestions as quick‑add buttons, reducing friction and encouraging richer data collection.

Persisting data and calculating emissions

Each activity maps to a carbon factor expressed in kilograms CO2 per unit. For example, driving a gasoline car emits 0.192 kg CO2 per kilometer, while a plant‑based meal emits about 0.5 kg per serving. The calculateCarbon helper reads the factor from a JSON lookup, multiplies by the user‑provided amount, and returns the result. Storing the raw amount alongside the computed carbonKg preserves auditability and allows retroactive factor updates without losing historical accuracy.

To give users a clear picture, aggregate the last 30 days of entries with a GraphQL query and render a bar chart using a lightweight library like Chart.js. Show both total kilograms and an equivalent number of tree‑months needed to offset the emissions, turning abstract numbers into visual impact.

Deploying and scaling the app

Containerize the API and the React build with Docker. A multi‑stage Dockerfile keeps the final image under 120 MB. Deploy to a platform that supports both HTTP and WebSocket traffic, such as Fly.io or Render, to keep the GraphQL subscription features alive for real‑time dashboards.

Enable caching on the GraphQL server with Apollo’s built‑in response cache for read‑only queries like entries. For the embedding lookup, store OpenAI vectors in a Postgres vector column (via the pgvector extension) to let the database compute cosine similarity efficiently, reducing latency from seconds to milliseconds.

Sources

1. EPA Greenhouse Gas Equivalencies Calculator
2. OpenAI API Documentation (embeddings endpoint)
3. Apollo GraphQL Server Documentation

Author: Mahmut Sarıkaya — sarikayadev.com

Tags: #AI carbon tracker #personal sustainability app #React #GraphQL API #OpenAI embeddings
Share:
M

Written by

Mahmut Sarıkaya

Software Developer

Comments

No comments yet. Be the first to share your thoughts!

Leave a Comment

2 + 6 =