[{"data":1,"prerenderedAt":3175},["ShallowReactive",2],{"/en/articles/building-my-first-fullstack-project":3,"seo-stack":3017,"seo-faq-data-en":3105},{"id":4,"title":5,"body":6,"date":2999,"description":3000,"extension":3001,"head":3002,"image":3003,"meta":3004,"navigation":200,"ogImage":3002,"path":3005,"readingTime":3006,"robots":3002,"schemaOrg":3002,"seo":3007,"sitemap":3008,"stem":3009,"tags":3010,"__hash__":3016},"articles_en/en/articles/building-my-first-fullstack-project.md","Building My First Full-Stack Project - Lessons Learned",{"type":7,"value":8,"toc":2985},"minimark",[9,14,18,25,30,33,68,71,75,80,83,88,142,147,315,321,325,330,333,338,787,1118,1122,1127,1196,1201,1479,1483,1488,1572,1577,1677,1682,1927,1931,1936,1939,2594,2598,2603,2630,2635,2702,2910,2914,2917,2955,2959,2965,2968,2972,2975,2981],[10,11,13],"h2",{"id":12},"introduction","Introduction",[15,16,17],"p",{},"Building my first full-stack application was both thrilling and terrifying. I made countless mistakes, hit numerous roadblocks, and almost gave up several times. But pushing through taught me more than any tutorial ever could. Here's my honest account of the journey.",[15,19,20],{},[21,22],"img",{"alt":23,"src":24},"preview","/articles/first-fullstack-project.jpg",[26,27,29],"h3",{"id":28},"the-project-a-task-management-app","The Project: A Task Management App",[15,31,32],{},"I decided to build a collaborative task management application—ambitious for a first project, I know. The stack I chose was:",[34,35,36,44,50,56,62],"ul",{},[37,38,39,43],"li",{},[40,41,42],"strong",{},"Frontend:"," Vue 3 + TypeScript + Tailwind CSS",[37,45,46,49],{},[40,47,48],{},"Backend:"," Node.js + Express",[37,51,52,55],{},[40,53,54],{},"Database:"," PostgreSQL",[37,57,58,61],{},[40,59,60],{},"Authentication:"," JWT",[37,63,64,67],{},[40,65,66],{},"Deployment:"," Docker + Railway",[15,69,70],{},"Looking back, I probably should have started simpler, but sometimes the best learning comes from jumping in the deep end.",[10,72,74],{"id":73},"lesson-1-planning-saves-time-even-when-it-feels-slow","Lesson 1: Planning Saves Time (Even When It Feels Slow)",[15,76,77],{},[40,78,79],{},"The Mistake",[15,81,82],{},"I was so eager to start coding that I skipped proper planning. I had a vague idea of \"a task app\" and just started building. Three weeks in, I realized I had built features that didn't work together and had to refactor everything.",[15,84,85],{},[40,86,87],{},"What I Should Have Done",[89,90,95],"pre",{"className":91,"code":92,"language":93,"meta":94,"style":94},"language-markdown shiki shiki-themes github-light github-dark github-dark","## Pre-Development Checklist\n- [ ] Define core features (MVP only)\n- [ ] Sketch basic UI wireframes\n- [ ] Design database schema\n- [ ] Plan API endpoints\n- [ ] Set up development environment\n- [ ] Create project structure\n","markdown","",[96,97,98,106,112,118,124,130,136],"code",{"__ignoreMap":94},[99,100,103],"span",{"class":101,"line":102},"line",1,[99,104,105],{},"## Pre-Development Checklist\n",[99,107,109],{"class":101,"line":108},2,[99,110,111],{},"- [ ] Define core features (MVP only)\n",[99,113,115],{"class":101,"line":114},3,[99,116,117],{},"- [ ] Sketch basic UI wireframes\n",[99,119,121],{"class":101,"line":120},4,[99,122,123],{},"- [ ] Design database schema\n",[99,125,127],{"class":101,"line":126},5,[99,128,129],{},"- [ ] Plan API endpoints\n",[99,131,133],{"class":101,"line":132},6,[99,134,135],{},"- [ ] Set up development environment\n",[99,137,139],{"class":101,"line":138},7,[99,140,141],{},"- [ ] Create project structure\n",[15,143,144],{},[40,145,146],{},"The Database Schema I Wish I Started With:",[89,148,152],{"className":149,"code":150,"language":151,"meta":94,"style":94},"language-sql shiki shiki-themes github-light github-dark github-dark","-- Users table\nCREATE TABLE users (\n  id SERIAL PRIMARY KEY,\n  email VARCHAR(255) UNIQUE NOT NULL,\n  password_hash VARCHAR(255) NOT NULL,\n  username VARCHAR(100) NOT NULL,\n  created_at TIMESTAMP DEFAULT NOW()\n);\n\n-- Projects table\nCREATE TABLE projects (\n  id SERIAL PRIMARY KEY,\n  name VARCHAR(255) NOT NULL,\n  description TEXT,\n  owner_id INTEGER REFERENCES users(id),\n  created_at TIMESTAMP DEFAULT NOW()\n);\n\n-- Tasks table\nCREATE TABLE tasks (\n  id SERIAL PRIMARY KEY,\n  title VARCHAR(255) NOT NULL,\n  description TEXT,\n  status VARCHAR(50) DEFAULT 'todo',\n  project_id INTEGER REFERENCES projects(id),\n  assigned_to INTEGER REFERENCES users(id),\n  created_at TIMESTAMP DEFAULT NOW(),\n  due_date TIMESTAMP\n);\n","sql",[96,153,154,159,164,169,174,179,184,189,195,202,208,214,219,225,231,237,242,247,252,258,264,269,275,280,286,292,298,304,310],{"__ignoreMap":94},[99,155,156],{"class":101,"line":102},[99,157,158],{},"-- Users table\n",[99,160,161],{"class":101,"line":108},[99,162,163],{},"CREATE TABLE users (\n",[99,165,166],{"class":101,"line":114},[99,167,168],{},"  id SERIAL PRIMARY KEY,\n",[99,170,171],{"class":101,"line":120},[99,172,173],{},"  email VARCHAR(255) UNIQUE NOT NULL,\n",[99,175,176],{"class":101,"line":126},[99,177,178],{},"  password_hash VARCHAR(255) NOT NULL,\n",[99,180,181],{"class":101,"line":132},[99,182,183],{},"  username VARCHAR(100) NOT NULL,\n",[99,185,186],{"class":101,"line":138},[99,187,188],{},"  created_at TIMESTAMP DEFAULT NOW()\n",[99,190,192],{"class":101,"line":191},8,[99,193,194],{},");\n",[99,196,198],{"class":101,"line":197},9,[99,199,201],{"emptyLinePlaceholder":200},true,"\n",[99,203,205],{"class":101,"line":204},10,[99,206,207],{},"-- Projects table\n",[99,209,211],{"class":101,"line":210},11,[99,212,213],{},"CREATE TABLE projects (\n",[99,215,217],{"class":101,"line":216},12,[99,218,168],{},[99,220,222],{"class":101,"line":221},13,[99,223,224],{},"  name VARCHAR(255) NOT NULL,\n",[99,226,228],{"class":101,"line":227},14,[99,229,230],{},"  description TEXT,\n",[99,232,234],{"class":101,"line":233},15,[99,235,236],{},"  owner_id INTEGER REFERENCES users(id),\n",[99,238,240],{"class":101,"line":239},16,[99,241,188],{},[99,243,245],{"class":101,"line":244},17,[99,246,194],{},[99,248,250],{"class":101,"line":249},18,[99,251,201],{"emptyLinePlaceholder":200},[99,253,255],{"class":101,"line":254},19,[99,256,257],{},"-- Tasks table\n",[99,259,261],{"class":101,"line":260},20,[99,262,263],{},"CREATE TABLE tasks (\n",[99,265,267],{"class":101,"line":266},21,[99,268,168],{},[99,270,272],{"class":101,"line":271},22,[99,273,274],{},"  title VARCHAR(255) NOT NULL,\n",[99,276,278],{"class":101,"line":277},23,[99,279,230],{},[99,281,283],{"class":101,"line":282},24,[99,284,285],{},"  status VARCHAR(50) DEFAULT 'todo',\n",[99,287,289],{"class":101,"line":288},25,[99,290,291],{},"  project_id INTEGER REFERENCES projects(id),\n",[99,293,295],{"class":101,"line":294},26,[99,296,297],{},"  assigned_to INTEGER REFERENCES users(id),\n",[99,299,301],{"class":101,"line":300},27,[99,302,303],{},"  created_at TIMESTAMP DEFAULT NOW(),\n",[99,305,307],{"class":101,"line":306},28,[99,308,309],{},"  due_date TIMESTAMP\n",[99,311,313],{"class":101,"line":312},29,[99,314,194],{},[15,316,317,320],{},[40,318,319],{},"Key Takeaway:"," Spend 20% of your time planning to save 50% of your development time.",[10,322,324],{"id":323},"lesson-2-start-with-authentication-its-harder-than-you-think","Lesson 2: Start With Authentication (It's Harder Than You Think)",[15,326,327],{},[40,328,329],{},"The Challenge",[15,331,332],{},"I naively thought, \"I'll add authentication later.\" Bad idea. Retrofitting auth into an existing app meant rewriting almost every API endpoint and component.",[15,334,335],{},[40,336,337],{},"My Authentication Flow (After Many Iterations):",[89,339,343],{"className":340,"code":341,"language":342,"meta":94,"style":94},"language-typescript shiki shiki-themes github-light github-dark github-dark","// Backend: Login endpoint\nimport { sign } from 'jsonwebtoken';\nimport { compare } from 'bcrypt';\n\nexport async function login(req, res) {\n  try {\n    const { email, password } = req.body;\n    \n    // Find user\n    const user = await db.query(\n      'SELECT * FROM users WHERE email = $1',\n      [email]\n    );\n    \n    if (!user.rows[0]) {\n      return res.status(401).json({ error: 'Invalid credentials' });\n    }\n    \n    // Verify password\n    const validPassword = await compare(\n      password,\n      user.rows[0].password_hash\n    );\n    \n    if (!validPassword) {\n      return res.status(401).json({ error: 'Invalid credentials' });\n    }\n    \n    // Generate JWT\n    const token = sign(\n      { userId: user.rows[0].id, email: user.rows[0].email },\n      process.env.JWT_SECRET!,\n      { expiresIn: '7d' }\n    );\n    \n    res.json({ token, user: { id: user.rows[0].id, email } });\n  } catch (error) {\n    res.status(500).json({ error: 'Server error' });\n  }\n}\n","typescript",[96,344,345,351,371,385,389,420,428,454,459,464,486,494,499,504,508,528,559,564,568,573,589,594,604,608,612,623,645,649,653,658,673,689,702,714,719,724,740,752,775,781],{"__ignoreMap":94},[99,346,347],{"class":101,"line":102},[99,348,350],{"class":349},"sCsY4","// Backend: Login endpoint\n",[99,352,353,357,361,364,368],{"class":101,"line":108},[99,354,356],{"class":355},"so5gQ","import",[99,358,360],{"class":359},"slsVL"," { sign } ",[99,362,363],{"class":355},"from",[99,365,367],{"class":366},"sfrk1"," 'jsonwebtoken'",[99,369,370],{"class":359},";\n",[99,372,373,375,378,380,383],{"class":101,"line":114},[99,374,356],{"class":355},[99,376,377],{"class":359}," { compare } ",[99,379,363],{"class":355},[99,381,382],{"class":366}," 'bcrypt'",[99,384,370],{"class":359},[99,386,387],{"class":101,"line":120},[99,388,201],{"emptyLinePlaceholder":200},[99,390,391,394,397,400,404,407,411,414,417],{"class":101,"line":126},[99,392,393],{"class":355},"export",[99,395,396],{"class":355}," async",[99,398,399],{"class":355}," function",[99,401,403],{"class":402},"shcOC"," login",[99,405,406],{"class":359},"(",[99,408,410],{"class":409},"sQHwn","req",[99,412,413],{"class":359},", ",[99,415,416],{"class":409},"res",[99,418,419],{"class":359},") {\n",[99,421,422,425],{"class":101,"line":132},[99,423,424],{"class":355},"  try",[99,426,427],{"class":359}," {\n",[99,429,430,433,436,440,442,445,448,451],{"class":101,"line":138},[99,431,432],{"class":355},"    const",[99,434,435],{"class":359}," { ",[99,437,439],{"class":438},"suiK_","email",[99,441,413],{"class":359},[99,443,444],{"class":438},"password",[99,446,447],{"class":359}," } ",[99,449,450],{"class":355},"=",[99,452,453],{"class":359}," req.body;\n",[99,455,456],{"class":101,"line":191},[99,457,458],{"class":359},"    \n",[99,460,461],{"class":101,"line":197},[99,462,463],{"class":349},"    // Find user\n",[99,465,466,468,471,474,477,480,483],{"class":101,"line":204},[99,467,432],{"class":355},[99,469,470],{"class":438}," user",[99,472,473],{"class":355}," =",[99,475,476],{"class":355}," await",[99,478,479],{"class":359}," db.",[99,481,482],{"class":402},"query",[99,484,485],{"class":359},"(\n",[99,487,488,491],{"class":101,"line":210},[99,489,490],{"class":366},"      'SELECT * FROM users WHERE email = $1'",[99,492,493],{"class":359},",\n",[99,495,496],{"class":101,"line":216},[99,497,498],{"class":359},"      [email]\n",[99,500,501],{"class":101,"line":221},[99,502,503],{"class":359},"    );\n",[99,505,506],{"class":101,"line":227},[99,507,458],{"class":359},[99,509,510,513,516,519,522,525],{"class":101,"line":233},[99,511,512],{"class":355},"    if",[99,514,515],{"class":359}," (",[99,517,518],{"class":355},"!",[99,520,521],{"class":359},"user.rows[",[99,523,524],{"class":438},"0",[99,526,527],{"class":359},"]) {\n",[99,529,530,533,536,539,541,544,547,550,553,556],{"class":101,"line":239},[99,531,532],{"class":355},"      return",[99,534,535],{"class":359}," res.",[99,537,538],{"class":402},"status",[99,540,406],{"class":359},[99,542,543],{"class":438},"401",[99,545,546],{"class":359},").",[99,548,549],{"class":402},"json",[99,551,552],{"class":359},"({ error: ",[99,554,555],{"class":366},"'Invalid credentials'",[99,557,558],{"class":359}," });\n",[99,560,561],{"class":101,"line":244},[99,562,563],{"class":359},"    }\n",[99,565,566],{"class":101,"line":249},[99,567,458],{"class":359},[99,569,570],{"class":101,"line":254},[99,571,572],{"class":349},"    // Verify password\n",[99,574,575,577,580,582,584,587],{"class":101,"line":260},[99,576,432],{"class":355},[99,578,579],{"class":438}," validPassword",[99,581,473],{"class":355},[99,583,476],{"class":355},[99,585,586],{"class":402}," compare",[99,588,485],{"class":359},[99,590,591],{"class":101,"line":266},[99,592,593],{"class":359},"      password,\n",[99,595,596,599,601],{"class":101,"line":271},[99,597,598],{"class":359},"      user.rows[",[99,600,524],{"class":438},[99,602,603],{"class":359},"].password_hash\n",[99,605,606],{"class":101,"line":277},[99,607,503],{"class":359},[99,609,610],{"class":101,"line":282},[99,611,458],{"class":359},[99,613,614,616,618,620],{"class":101,"line":288},[99,615,512],{"class":355},[99,617,515],{"class":359},[99,619,518],{"class":355},[99,621,622],{"class":359},"validPassword) {\n",[99,624,625,627,629,631,633,635,637,639,641,643],{"class":101,"line":294},[99,626,532],{"class":355},[99,628,535],{"class":359},[99,630,538],{"class":402},[99,632,406],{"class":359},[99,634,543],{"class":438},[99,636,546],{"class":359},[99,638,549],{"class":402},[99,640,552],{"class":359},[99,642,555],{"class":366},[99,644,558],{"class":359},[99,646,647],{"class":101,"line":300},[99,648,563],{"class":359},[99,650,651],{"class":101,"line":306},[99,652,458],{"class":359},[99,654,655],{"class":101,"line":312},[99,656,657],{"class":349},"    // Generate JWT\n",[99,659,661,663,666,668,671],{"class":101,"line":660},30,[99,662,432],{"class":355},[99,664,665],{"class":438}," token",[99,667,473],{"class":355},[99,669,670],{"class":402}," sign",[99,672,485],{"class":359},[99,674,676,679,681,684,686],{"class":101,"line":675},31,[99,677,678],{"class":359},"      { userId: user.rows[",[99,680,524],{"class":438},[99,682,683],{"class":359},"].id, email: user.rows[",[99,685,524],{"class":438},[99,687,688],{"class":359},"].email },\n",[99,690,692,695,698,700],{"class":101,"line":691},32,[99,693,694],{"class":359},"      process.env.",[99,696,697],{"class":438},"JWT_SECRET",[99,699,518],{"class":355},[99,701,493],{"class":359},[99,703,705,708,711],{"class":101,"line":704},33,[99,706,707],{"class":359},"      { expiresIn: ",[99,709,710],{"class":366},"'7d'",[99,712,713],{"class":359}," }\n",[99,715,717],{"class":101,"line":716},34,[99,718,503],{"class":359},[99,720,722],{"class":101,"line":721},35,[99,723,458],{"class":359},[99,725,727,730,732,735,737],{"class":101,"line":726},36,[99,728,729],{"class":359},"    res.",[99,731,549],{"class":402},[99,733,734],{"class":359},"({ token, user: { id: user.rows[",[99,736,524],{"class":438},[99,738,739],{"class":359},"].id, email } });\n",[99,741,743,746,749],{"class":101,"line":742},37,[99,744,745],{"class":359},"  } ",[99,747,748],{"class":355},"catch",[99,750,751],{"class":359}," (error) {\n",[99,753,755,757,759,761,764,766,768,770,773],{"class":101,"line":754},38,[99,756,729],{"class":359},[99,758,538],{"class":402},[99,760,406],{"class":359},[99,762,763],{"class":438},"500",[99,765,546],{"class":359},[99,767,549],{"class":402},[99,769,552],{"class":359},[99,771,772],{"class":366},"'Server error'",[99,774,558],{"class":359},[99,776,778],{"class":101,"line":777},39,[99,779,780],{"class":359},"  }\n",[99,782,784],{"class":101,"line":783},40,[99,785,786],{"class":359},"}\n",[89,788,790],{"className":340,"code":789,"language":342,"meta":94,"style":94},"// Frontend: Auth composable\nexport function useAuth() {\n  const token = ref(localStorage.getItem('token'));\n  const user = ref(null);\n  \n  async function login(email: string, password: string) {\n    const response = await fetch('/api/auth/login', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify({ email, password })\n    });\n    \n    if (!response.ok) throw new Error('Login failed');\n    \n    const data = await response.json();\n    token.value = data.token;\n    user.value = data.user;\n    localStorage.setItem('token', data.token);\n  }\n  \n  function logout() {\n    token.value = null;\n    user.value = null;\n    localStorage.removeItem('token');\n  }\n  \n  return { token, user, login, logout };\n}\n",[96,791,792,797,809,835,852,857,886,908,918,935,952,957,961,988,992,1011,1021,1031,1046,1050,1054,1064,1075,1085,1098,1102,1106,1114],{"__ignoreMap":94},[99,793,794],{"class":101,"line":102},[99,795,796],{"class":349},"// Frontend: Auth composable\n",[99,798,799,801,803,806],{"class":101,"line":108},[99,800,393],{"class":355},[99,802,399],{"class":355},[99,804,805],{"class":402}," useAuth",[99,807,808],{"class":359},"() {\n",[99,810,811,814,816,818,821,824,827,829,832],{"class":101,"line":114},[99,812,813],{"class":355},"  const",[99,815,665],{"class":438},[99,817,473],{"class":355},[99,819,820],{"class":402}," ref",[99,822,823],{"class":359},"(localStorage.",[99,825,826],{"class":402},"getItem",[99,828,406],{"class":359},[99,830,831],{"class":366},"'token'",[99,833,834],{"class":359},"));\n",[99,836,837,839,841,843,845,847,850],{"class":101,"line":120},[99,838,813],{"class":355},[99,840,470],{"class":438},[99,842,473],{"class":355},[99,844,820],{"class":402},[99,846,406],{"class":359},[99,848,849],{"class":438},"null",[99,851,194],{"class":359},[99,853,854],{"class":101,"line":126},[99,855,856],{"class":359},"  \n",[99,858,859,862,864,866,868,870,873,876,878,880,882,884],{"class":101,"line":132},[99,860,861],{"class":355},"  async",[99,863,399],{"class":355},[99,865,403],{"class":402},[99,867,406],{"class":359},[99,869,439],{"class":409},[99,871,872],{"class":355},":",[99,874,875],{"class":438}," string",[99,877,413],{"class":359},[99,879,444],{"class":409},[99,881,872],{"class":355},[99,883,875],{"class":438},[99,885,419],{"class":359},[99,887,888,890,893,895,897,900,902,905],{"class":101,"line":138},[99,889,432],{"class":355},[99,891,892],{"class":438}," response",[99,894,473],{"class":355},[99,896,476],{"class":355},[99,898,899],{"class":402}," fetch",[99,901,406],{"class":359},[99,903,904],{"class":366},"'/api/auth/login'",[99,906,907],{"class":359},", {\n",[99,909,910,913,916],{"class":101,"line":191},[99,911,912],{"class":359},"      method: ",[99,914,915],{"class":366},"'POST'",[99,917,493],{"class":359},[99,919,920,923,926,929,932],{"class":101,"line":197},[99,921,922],{"class":359},"      headers: { ",[99,924,925],{"class":366},"'Content-Type'",[99,927,928],{"class":359},": ",[99,930,931],{"class":366},"'application/json'",[99,933,934],{"class":359}," },\n",[99,936,937,940,943,946,949],{"class":101,"line":204},[99,938,939],{"class":359},"      body: ",[99,941,942],{"class":438},"JSON",[99,944,945],{"class":359},".",[99,947,948],{"class":402},"stringify",[99,950,951],{"class":359},"({ email, password })\n",[99,953,954],{"class":101,"line":210},[99,955,956],{"class":359},"    });\n",[99,958,959],{"class":101,"line":216},[99,960,458],{"class":359},[99,962,963,965,967,969,972,975,978,981,983,986],{"class":101,"line":221},[99,964,512],{"class":355},[99,966,515],{"class":359},[99,968,518],{"class":355},[99,970,971],{"class":359},"response.ok) ",[99,973,974],{"class":355},"throw",[99,976,977],{"class":355}," new",[99,979,980],{"class":402}," Error",[99,982,406],{"class":359},[99,984,985],{"class":366},"'Login failed'",[99,987,194],{"class":359},[99,989,990],{"class":101,"line":227},[99,991,458],{"class":359},[99,993,994,996,999,1001,1003,1006,1008],{"class":101,"line":233},[99,995,432],{"class":355},[99,997,998],{"class":438}," data",[99,1000,473],{"class":355},[99,1002,476],{"class":355},[99,1004,1005],{"class":359}," response.",[99,1007,549],{"class":402},[99,1009,1010],{"class":359},"();\n",[99,1012,1013,1016,1018],{"class":101,"line":239},[99,1014,1015],{"class":359},"    token.value ",[99,1017,450],{"class":355},[99,1019,1020],{"class":359}," data.token;\n",[99,1022,1023,1026,1028],{"class":101,"line":244},[99,1024,1025],{"class":359},"    user.value ",[99,1027,450],{"class":355},[99,1029,1030],{"class":359}," data.user;\n",[99,1032,1033,1036,1039,1041,1043],{"class":101,"line":249},[99,1034,1035],{"class":359},"    localStorage.",[99,1037,1038],{"class":402},"setItem",[99,1040,406],{"class":359},[99,1042,831],{"class":366},[99,1044,1045],{"class":359},", data.token);\n",[99,1047,1048],{"class":101,"line":254},[99,1049,780],{"class":359},[99,1051,1052],{"class":101,"line":260},[99,1053,856],{"class":359},[99,1055,1056,1059,1062],{"class":101,"line":266},[99,1057,1058],{"class":355},"  function",[99,1060,1061],{"class":402}," logout",[99,1063,808],{"class":359},[99,1065,1066,1068,1070,1073],{"class":101,"line":271},[99,1067,1015],{"class":359},[99,1069,450],{"class":355},[99,1071,1072],{"class":438}," null",[99,1074,370],{"class":359},[99,1076,1077,1079,1081,1083],{"class":101,"line":277},[99,1078,1025],{"class":359},[99,1080,450],{"class":355},[99,1082,1072],{"class":438},[99,1084,370],{"class":359},[99,1086,1087,1089,1092,1094,1096],{"class":101,"line":282},[99,1088,1035],{"class":359},[99,1090,1091],{"class":402},"removeItem",[99,1093,406],{"class":359},[99,1095,831],{"class":366},[99,1097,194],{"class":359},[99,1099,1100],{"class":101,"line":288},[99,1101,780],{"class":359},[99,1103,1104],{"class":101,"line":294},[99,1105,856],{"class":359},[99,1107,1108,1111],{"class":101,"line":300},[99,1109,1110],{"class":355},"  return",[99,1112,1113],{"class":359}," { token, user, login, logout };\n",[99,1115,1116],{"class":101,"line":306},[99,1117,786],{"class":359},[10,1119,1121],{"id":1120},"lesson-3-error-handling-isnt-optional","Lesson 3: Error Handling Isn't Optional",[15,1123,1124],{},[40,1125,1126],{},"My Initial Approach (Don't Do This)",[89,1128,1130],{"className":340,"code":1129,"language":342,"meta":94,"style":94},"// ❌ Bad: No error handling\nasync function fetchTasks() {\n  const response = await fetch('/api/tasks');\n  const tasks = await response.json();\n  return tasks;\n}\n",[96,1131,1132,1137,1149,1168,1185,1192],{"__ignoreMap":94},[99,1133,1134],{"class":101,"line":102},[99,1135,1136],{"class":349},"// ❌ Bad: No error handling\n",[99,1138,1139,1142,1144,1147],{"class":101,"line":108},[99,1140,1141],{"class":355},"async",[99,1143,399],{"class":355},[99,1145,1146],{"class":402}," fetchTasks",[99,1148,808],{"class":359},[99,1150,1151,1153,1155,1157,1159,1161,1163,1166],{"class":101,"line":114},[99,1152,813],{"class":355},[99,1154,892],{"class":438},[99,1156,473],{"class":355},[99,1158,476],{"class":355},[99,1160,899],{"class":402},[99,1162,406],{"class":359},[99,1164,1165],{"class":366},"'/api/tasks'",[99,1167,194],{"class":359},[99,1169,1170,1172,1175,1177,1179,1181,1183],{"class":101,"line":120},[99,1171,813],{"class":355},[99,1173,1174],{"class":438}," tasks",[99,1176,473],{"class":355},[99,1178,476],{"class":355},[99,1180,1005],{"class":359},[99,1182,549],{"class":402},[99,1184,1010],{"class":359},[99,1186,1187,1189],{"class":101,"line":126},[99,1188,1110],{"class":355},[99,1190,1191],{"class":359}," tasks;\n",[99,1193,1194],{"class":101,"line":132},[99,1195,786],{"class":359},[15,1197,1198],{},[40,1199,1200],{},"What I Learned to Do",[89,1202,1204],{"className":340,"code":1203,"language":342,"meta":94,"style":94},"// ✅ Good: Proper error handling\nasync function fetchTasks() {\n  try {\n    const response = await fetch('/api/tasks', {\n      headers: {\n        'Authorization': `Bearer ${token.value}`\n      }\n    });\n    \n    if (!response.ok) {\n      if (response.status === 401) {\n        // Token expired, redirect to login\n        await logout();\n        router.push('/login');\n        throw new Error('Session expired');\n      }\n      throw new Error(`HTTP error! status: ${response.status}`);\n    }\n    \n    const data = await response.json();\n    return data;\n  } catch (error) {\n    console.error('Failed to fetch tasks:', error);\n    // Show user-friendly error message\n    showNotification('Failed to load tasks. Please try again.', 'error');\n    return [];\n  }\n}\n",[96,1205,1206,1211,1221,1227,1245,1250,1271,1276,1280,1284,1295,1311,1316,1325,1340,1356,1360,1386,1390,1394,1410,1418,1426,1442,1447,1464,1471,1475],{"__ignoreMap":94},[99,1207,1208],{"class":101,"line":102},[99,1209,1210],{"class":349},"// ✅ Good: Proper error handling\n",[99,1212,1213,1215,1217,1219],{"class":101,"line":108},[99,1214,1141],{"class":355},[99,1216,399],{"class":355},[99,1218,1146],{"class":402},[99,1220,808],{"class":359},[99,1222,1223,1225],{"class":101,"line":114},[99,1224,424],{"class":355},[99,1226,427],{"class":359},[99,1228,1229,1231,1233,1235,1237,1239,1241,1243],{"class":101,"line":120},[99,1230,432],{"class":355},[99,1232,892],{"class":438},[99,1234,473],{"class":355},[99,1236,476],{"class":355},[99,1238,899],{"class":402},[99,1240,406],{"class":359},[99,1242,1165],{"class":366},[99,1244,907],{"class":359},[99,1246,1247],{"class":101,"line":126},[99,1248,1249],{"class":359},"      headers: {\n",[99,1251,1252,1255,1257,1260,1263,1265,1268],{"class":101,"line":132},[99,1253,1254],{"class":366},"        'Authorization'",[99,1256,928],{"class":359},[99,1258,1259],{"class":366},"`Bearer ${",[99,1261,1262],{"class":359},"token",[99,1264,945],{"class":366},[99,1266,1267],{"class":359},"value",[99,1269,1270],{"class":366},"}`\n",[99,1272,1273],{"class":101,"line":138},[99,1274,1275],{"class":359},"      }\n",[99,1277,1278],{"class":101,"line":191},[99,1279,956],{"class":359},[99,1281,1282],{"class":101,"line":197},[99,1283,458],{"class":359},[99,1285,1286,1288,1290,1292],{"class":101,"line":204},[99,1287,512],{"class":355},[99,1289,515],{"class":359},[99,1291,518],{"class":355},[99,1293,1294],{"class":359},"response.ok) {\n",[99,1296,1297,1300,1303,1306,1309],{"class":101,"line":210},[99,1298,1299],{"class":355},"      if",[99,1301,1302],{"class":359}," (response.status ",[99,1304,1305],{"class":355},"===",[99,1307,1308],{"class":438}," 401",[99,1310,419],{"class":359},[99,1312,1313],{"class":101,"line":216},[99,1314,1315],{"class":349},"        // Token expired, redirect to login\n",[99,1317,1318,1321,1323],{"class":101,"line":221},[99,1319,1320],{"class":355},"        await",[99,1322,1061],{"class":402},[99,1324,1010],{"class":359},[99,1326,1327,1330,1333,1335,1338],{"class":101,"line":227},[99,1328,1329],{"class":359},"        router.",[99,1331,1332],{"class":402},"push",[99,1334,406],{"class":359},[99,1336,1337],{"class":366},"'/login'",[99,1339,194],{"class":359},[99,1341,1342,1345,1347,1349,1351,1354],{"class":101,"line":233},[99,1343,1344],{"class":355},"        throw",[99,1346,977],{"class":355},[99,1348,980],{"class":402},[99,1350,406],{"class":359},[99,1352,1353],{"class":366},"'Session expired'",[99,1355,194],{"class":359},[99,1357,1358],{"class":101,"line":239},[99,1359,1275],{"class":359},[99,1361,1362,1365,1367,1369,1371,1374,1377,1379,1381,1384],{"class":101,"line":244},[99,1363,1364],{"class":355},"      throw",[99,1366,977],{"class":355},[99,1368,980],{"class":402},[99,1370,406],{"class":359},[99,1372,1373],{"class":366},"`HTTP error! status: ${",[99,1375,1376],{"class":359},"response",[99,1378,945],{"class":366},[99,1380,538],{"class":359},[99,1382,1383],{"class":366},"}`",[99,1385,194],{"class":359},[99,1387,1388],{"class":101,"line":249},[99,1389,563],{"class":359},[99,1391,1392],{"class":101,"line":254},[99,1393,458],{"class":359},[99,1395,1396,1398,1400,1402,1404,1406,1408],{"class":101,"line":260},[99,1397,432],{"class":355},[99,1399,998],{"class":438},[99,1401,473],{"class":355},[99,1403,476],{"class":355},[99,1405,1005],{"class":359},[99,1407,549],{"class":402},[99,1409,1010],{"class":359},[99,1411,1412,1415],{"class":101,"line":266},[99,1413,1414],{"class":355},"    return",[99,1416,1417],{"class":359}," data;\n",[99,1419,1420,1422,1424],{"class":101,"line":271},[99,1421,745],{"class":359},[99,1423,748],{"class":355},[99,1425,751],{"class":359},[99,1427,1428,1431,1434,1436,1439],{"class":101,"line":277},[99,1429,1430],{"class":359},"    console.",[99,1432,1433],{"class":402},"error",[99,1435,406],{"class":359},[99,1437,1438],{"class":366},"'Failed to fetch tasks:'",[99,1440,1441],{"class":359},", error);\n",[99,1443,1444],{"class":101,"line":282},[99,1445,1446],{"class":349},"    // Show user-friendly error message\n",[99,1448,1449,1452,1454,1457,1459,1462],{"class":101,"line":288},[99,1450,1451],{"class":402},"    showNotification",[99,1453,406],{"class":359},[99,1455,1456],{"class":366},"'Failed to load tasks. Please try again.'",[99,1458,413],{"class":359},[99,1460,1461],{"class":366},"'error'",[99,1463,194],{"class":359},[99,1465,1466,1468],{"class":101,"line":294},[99,1467,1414],{"class":355},[99,1469,1470],{"class":359}," [];\n",[99,1472,1473],{"class":101,"line":300},[99,1474,780],{"class":359},[99,1476,1477],{"class":101,"line":306},[99,1478,786],{"class":359},[10,1480,1482],{"id":1481},"lesson-4-api-design-matters","Lesson 4: API Design Matters",[15,1484,1485],{},[40,1486,1487],{},"My First API Attempt",[89,1489,1491],{"className":340,"code":1490,"language":342,"meta":94,"style":94},"// ❌ Inconsistent and confusing\napp.get('/getTasks', ...)\napp.post('/task/create', ...)\napp.post('/deleteTask', ...)\napp.put('/tasks/update/:id', ...)\n",[96,1492,1493,1498,1519,1537,1554],{"__ignoreMap":94},[99,1494,1495],{"class":101,"line":102},[99,1496,1497],{"class":349},"// ❌ Inconsistent and confusing\n",[99,1499,1500,1503,1506,1508,1511,1513,1516],{"class":101,"line":108},[99,1501,1502],{"class":359},"app.",[99,1504,1505],{"class":402},"get",[99,1507,406],{"class":359},[99,1509,1510],{"class":366},"'/getTasks'",[99,1512,413],{"class":359},[99,1514,1515],{"class":355},"...",[99,1517,1518],{"class":359},")\n",[99,1520,1521,1523,1526,1528,1531,1533,1535],{"class":101,"line":114},[99,1522,1502],{"class":359},[99,1524,1525],{"class":402},"post",[99,1527,406],{"class":359},[99,1529,1530],{"class":366},"'/task/create'",[99,1532,413],{"class":359},[99,1534,1515],{"class":355},[99,1536,1518],{"class":359},[99,1538,1539,1541,1543,1545,1548,1550,1552],{"class":101,"line":120},[99,1540,1502],{"class":359},[99,1542,1525],{"class":402},[99,1544,406],{"class":359},[99,1546,1547],{"class":366},"'/deleteTask'",[99,1549,413],{"class":359},[99,1551,1515],{"class":355},[99,1553,1518],{"class":359},[99,1555,1556,1558,1561,1563,1566,1568,1570],{"class":101,"line":126},[99,1557,1502],{"class":359},[99,1559,1560],{"class":402},"put",[99,1562,406],{"class":359},[99,1564,1565],{"class":366},"'/tasks/update/:id'",[99,1567,413],{"class":359},[99,1569,1515],{"class":355},[99,1571,1518],{"class":359},[15,1573,1574],{},[40,1575,1576],{},"After Learning REST Principles",[89,1578,1580],{"className":340,"code":1579,"language":342,"meta":94,"style":94},"// ✅ RESTful and consistent\napp.get('/api/tasks', getAllTasks);\napp.get('/api/tasks/:id', getTaskById);\napp.post('/api/tasks', createTask);\napp.put('/api/tasks/:id', updateTask);\napp.delete('/api/tasks/:id', deleteTask);\n\n// Nested resources\napp.get('/api/projects/:projectId/tasks', getProjectTasks);\n",[96,1581,1582,1587,1600,1614,1627,1640,1654,1658,1663],{"__ignoreMap":94},[99,1583,1584],{"class":101,"line":102},[99,1585,1586],{"class":349},"// ✅ RESTful and consistent\n",[99,1588,1589,1591,1593,1595,1597],{"class":101,"line":108},[99,1590,1502],{"class":359},[99,1592,1505],{"class":402},[99,1594,406],{"class":359},[99,1596,1165],{"class":366},[99,1598,1599],{"class":359},", getAllTasks);\n",[99,1601,1602,1604,1606,1608,1611],{"class":101,"line":114},[99,1603,1502],{"class":359},[99,1605,1505],{"class":402},[99,1607,406],{"class":359},[99,1609,1610],{"class":366},"'/api/tasks/:id'",[99,1612,1613],{"class":359},", getTaskById);\n",[99,1615,1616,1618,1620,1622,1624],{"class":101,"line":120},[99,1617,1502],{"class":359},[99,1619,1525],{"class":402},[99,1621,406],{"class":359},[99,1623,1165],{"class":366},[99,1625,1626],{"class":359},", createTask);\n",[99,1628,1629,1631,1633,1635,1637],{"class":101,"line":126},[99,1630,1502],{"class":359},[99,1632,1560],{"class":402},[99,1634,406],{"class":359},[99,1636,1610],{"class":366},[99,1638,1639],{"class":359},", updateTask);\n",[99,1641,1642,1644,1647,1649,1651],{"class":101,"line":132},[99,1643,1502],{"class":359},[99,1645,1646],{"class":402},"delete",[99,1648,406],{"class":359},[99,1650,1610],{"class":366},[99,1652,1653],{"class":359},", deleteTask);\n",[99,1655,1656],{"class":101,"line":138},[99,1657,201],{"emptyLinePlaceholder":200},[99,1659,1660],{"class":101,"line":191},[99,1661,1662],{"class":349},"// Nested resources\n",[99,1664,1665,1667,1669,1671,1674],{"class":101,"line":197},[99,1666,1502],{"class":359},[99,1668,1505],{"class":402},[99,1670,406],{"class":359},[99,1672,1673],{"class":366},"'/api/projects/:projectId/tasks'",[99,1675,1676],{"class":359},", getProjectTasks);\n",[15,1678,1679],{},[40,1680,1681],{},"With Proper Middleware",[89,1683,1685],{"className":340,"code":1684,"language":342,"meta":94,"style":94},"import { authenticate } from './middleware/auth';\n\n// Protected routes\napp.use('/api/tasks', authenticate);\napp.use('/api/projects', authenticate);\n\n// Middleware\nexport async function authenticate(req, res, next) {\n  try {\n    const token = req.headers.authorization?.split(' ')[1];\n    \n    if (!token) {\n      return res.status(401).json({ error: 'No token provided' });\n    }\n    \n    const decoded = verify(token, process.env.JWT_SECRET!);\n    req.user = decoded;\n    next();\n  } catch (error) {\n    res.status(401).json({ error: 'Invalid token' });\n  }\n}\n",[96,1686,1687,1701,1705,1710,1724,1737,1741,1746,1772,1778,1806,1810,1821,1844,1848,1852,1873,1883,1890,1898,1919,1923],{"__ignoreMap":94},[99,1688,1689,1691,1694,1696,1699],{"class":101,"line":102},[99,1690,356],{"class":355},[99,1692,1693],{"class":359}," { authenticate } ",[99,1695,363],{"class":355},[99,1697,1698],{"class":366}," './middleware/auth'",[99,1700,370],{"class":359},[99,1702,1703],{"class":101,"line":108},[99,1704,201],{"emptyLinePlaceholder":200},[99,1706,1707],{"class":101,"line":114},[99,1708,1709],{"class":349},"// Protected routes\n",[99,1711,1712,1714,1717,1719,1721],{"class":101,"line":120},[99,1713,1502],{"class":359},[99,1715,1716],{"class":402},"use",[99,1718,406],{"class":359},[99,1720,1165],{"class":366},[99,1722,1723],{"class":359},", authenticate);\n",[99,1725,1726,1728,1730,1732,1735],{"class":101,"line":126},[99,1727,1502],{"class":359},[99,1729,1716],{"class":402},[99,1731,406],{"class":359},[99,1733,1734],{"class":366},"'/api/projects'",[99,1736,1723],{"class":359},[99,1738,1739],{"class":101,"line":132},[99,1740,201],{"emptyLinePlaceholder":200},[99,1742,1743],{"class":101,"line":138},[99,1744,1745],{"class":349},"// Middleware\n",[99,1747,1748,1750,1752,1754,1757,1759,1761,1763,1765,1767,1770],{"class":101,"line":191},[99,1749,393],{"class":355},[99,1751,396],{"class":355},[99,1753,399],{"class":355},[99,1755,1756],{"class":402}," authenticate",[99,1758,406],{"class":359},[99,1760,410],{"class":409},[99,1762,413],{"class":359},[99,1764,416],{"class":409},[99,1766,413],{"class":359},[99,1768,1769],{"class":409},"next",[99,1771,419],{"class":359},[99,1773,1774,1776],{"class":101,"line":197},[99,1775,424],{"class":355},[99,1777,427],{"class":359},[99,1779,1780,1782,1784,1786,1789,1792,1794,1797,1800,1803],{"class":101,"line":204},[99,1781,432],{"class":355},[99,1783,665],{"class":438},[99,1785,473],{"class":355},[99,1787,1788],{"class":359}," req.headers.authorization?.",[99,1790,1791],{"class":402},"split",[99,1793,406],{"class":359},[99,1795,1796],{"class":366},"' '",[99,1798,1799],{"class":359},")[",[99,1801,1802],{"class":438},"1",[99,1804,1805],{"class":359},"];\n",[99,1807,1808],{"class":101,"line":210},[99,1809,458],{"class":359},[99,1811,1812,1814,1816,1818],{"class":101,"line":216},[99,1813,512],{"class":355},[99,1815,515],{"class":359},[99,1817,518],{"class":355},[99,1819,1820],{"class":359},"token) {\n",[99,1822,1823,1825,1827,1829,1831,1833,1835,1837,1839,1842],{"class":101,"line":221},[99,1824,532],{"class":355},[99,1826,535],{"class":359},[99,1828,538],{"class":402},[99,1830,406],{"class":359},[99,1832,543],{"class":438},[99,1834,546],{"class":359},[99,1836,549],{"class":402},[99,1838,552],{"class":359},[99,1840,1841],{"class":366},"'No token provided'",[99,1843,558],{"class":359},[99,1845,1846],{"class":101,"line":227},[99,1847,563],{"class":359},[99,1849,1850],{"class":101,"line":233},[99,1851,458],{"class":359},[99,1853,1854,1856,1859,1861,1864,1867,1869,1871],{"class":101,"line":239},[99,1855,432],{"class":355},[99,1857,1858],{"class":438}," decoded",[99,1860,473],{"class":355},[99,1862,1863],{"class":402}," verify",[99,1865,1866],{"class":359},"(token, process.env.",[99,1868,697],{"class":438},[99,1870,518],{"class":355},[99,1872,194],{"class":359},[99,1874,1875,1878,1880],{"class":101,"line":244},[99,1876,1877],{"class":359},"    req.user ",[99,1879,450],{"class":355},[99,1881,1882],{"class":359}," decoded;\n",[99,1884,1885,1888],{"class":101,"line":249},[99,1886,1887],{"class":402},"    next",[99,1889,1010],{"class":359},[99,1891,1892,1894,1896],{"class":101,"line":254},[99,1893,745],{"class":359},[99,1895,748],{"class":355},[99,1897,751],{"class":359},[99,1899,1900,1902,1904,1906,1908,1910,1912,1914,1917],{"class":101,"line":260},[99,1901,729],{"class":359},[99,1903,538],{"class":402},[99,1905,406],{"class":359},[99,1907,543],{"class":438},[99,1909,546],{"class":359},[99,1911,549],{"class":402},[99,1913,552],{"class":359},[99,1915,1916],{"class":366},"'Invalid token'",[99,1918,558],{"class":359},[99,1920,1921],{"class":101,"line":266},[99,1922,780],{"class":359},[99,1924,1925],{"class":101,"line":271},[99,1926,786],{"class":359},[10,1928,1930],{"id":1929},"lesson-5-state-management-gets-complex-fast","Lesson 5: State Management Gets Complex Fast",[15,1932,1933],{},[40,1934,1935],{},"When I Realized I Needed Pinia",[15,1937,1938],{},"Initially, I passed props and emitted events everywhere. With 3+ levels of components, it became a nightmare. Enter Pinia.",[89,1940,1942],{"className":340,"code":1941,"language":342,"meta":94,"style":94},"// stores/tasks.ts\nimport { defineStore } from 'pinia';\n\ninterface Task {\n  id: number;\n  title: string;\n  description: string;\n  status: 'todo' | 'in-progress' | 'done';\n  projectId: number;\n}\n\nexport const useTasksStore = defineStore('tasks', {\n  state: () => ({\n    tasks: [] as Task[],\n    loading: false,\n    error: null as string | null\n  }),\n  \n  getters: {\n    tasksByProject: (state) => (projectId: number) => {\n      return state.tasks.filter(task => task.projectId === projectId);\n    },\n    tasksByStatus: (state) => (status: Task['status']) => {\n      return state.tasks.filter(task => task.status === status);\n    }\n  },\n  \n  actions: {\n    async fetchTasks() {\n      this.loading = true;\n      this.error = null;\n      try {\n        const response = await fetch('/api/tasks');\n        this.tasks = await response.json();\n      } catch (error) {\n        this.error = 'Failed to fetch tasks';\n      } finally {\n        this.loading = false;\n      }\n    },\n    \n    async createTask(task: Omit\u003CTask, 'id'>) {\n      try {\n        const response = await fetch('/api/tasks', {\n          method: 'POST',\n          headers: { 'Content-Type': 'application/json' },\n          body: JSON.stringify(task)\n        });\n        const newTask = await response.json();\n        this.tasks.push(newTask);\n      } catch (error) {\n        this.error = 'Failed to create task';\n      }\n    }\n  }\n});\n",[96,1943,1944,1949,1963,1967,1977,1989,2000,2011,2034,2045,2049,2053,2075,2089,2102,2112,2129,2134,2138,2143,2174,2200,2205,2239,2261,2265,2270,2274,2279,2288,2303,2316,2323,2342,2360,2369,2382,2391,2404,2408,2412,2417,2448,2455,2474,2484,2498,2513,2519,2537,2550,2559,2573,2578,2583,2588],{"__ignoreMap":94},[99,1945,1946],{"class":101,"line":102},[99,1947,1948],{"class":349},"// stores/tasks.ts\n",[99,1950,1951,1953,1956,1958,1961],{"class":101,"line":108},[99,1952,356],{"class":355},[99,1954,1955],{"class":359}," { defineStore } ",[99,1957,363],{"class":355},[99,1959,1960],{"class":366}," 'pinia'",[99,1962,370],{"class":359},[99,1964,1965],{"class":101,"line":114},[99,1966,201],{"emptyLinePlaceholder":200},[99,1968,1969,1972,1975],{"class":101,"line":120},[99,1970,1971],{"class":355},"interface",[99,1973,1974],{"class":402}," Task",[99,1976,427],{"class":359},[99,1978,1979,1982,1984,1987],{"class":101,"line":126},[99,1980,1981],{"class":409},"  id",[99,1983,872],{"class":355},[99,1985,1986],{"class":438}," number",[99,1988,370],{"class":359},[99,1990,1991,1994,1996,1998],{"class":101,"line":132},[99,1992,1993],{"class":409},"  title",[99,1995,872],{"class":355},[99,1997,875],{"class":438},[99,1999,370],{"class":359},[99,2001,2002,2005,2007,2009],{"class":101,"line":138},[99,2003,2004],{"class":409},"  description",[99,2006,872],{"class":355},[99,2008,875],{"class":438},[99,2010,370],{"class":359},[99,2012,2013,2016,2018,2021,2024,2027,2029,2032],{"class":101,"line":191},[99,2014,2015],{"class":409},"  status",[99,2017,872],{"class":355},[99,2019,2020],{"class":366}," 'todo'",[99,2022,2023],{"class":355}," |",[99,2025,2026],{"class":366}," 'in-progress'",[99,2028,2023],{"class":355},[99,2030,2031],{"class":366}," 'done'",[99,2033,370],{"class":359},[99,2035,2036,2039,2041,2043],{"class":101,"line":197},[99,2037,2038],{"class":409},"  projectId",[99,2040,872],{"class":355},[99,2042,1986],{"class":438},[99,2044,370],{"class":359},[99,2046,2047],{"class":101,"line":204},[99,2048,786],{"class":359},[99,2050,2051],{"class":101,"line":210},[99,2052,201],{"emptyLinePlaceholder":200},[99,2054,2055,2057,2060,2063,2065,2068,2070,2073],{"class":101,"line":216},[99,2056,393],{"class":355},[99,2058,2059],{"class":355}," const",[99,2061,2062],{"class":438}," useTasksStore",[99,2064,473],{"class":355},[99,2066,2067],{"class":402}," defineStore",[99,2069,406],{"class":359},[99,2071,2072],{"class":366},"'tasks'",[99,2074,907],{"class":359},[99,2076,2077,2080,2083,2086],{"class":101,"line":221},[99,2078,2079],{"class":402},"  state",[99,2081,2082],{"class":359},": () ",[99,2084,2085],{"class":355},"=>",[99,2087,2088],{"class":359}," ({\n",[99,2090,2091,2094,2097,2099],{"class":101,"line":227},[99,2092,2093],{"class":359},"    tasks: [] ",[99,2095,2096],{"class":355},"as",[99,2098,1974],{"class":402},[99,2100,2101],{"class":359},"[],\n",[99,2103,2104,2107,2110],{"class":101,"line":233},[99,2105,2106],{"class":359},"    loading: ",[99,2108,2109],{"class":438},"false",[99,2111,493],{"class":359},[99,2113,2114,2117,2119,2122,2124,2126],{"class":101,"line":239},[99,2115,2116],{"class":359},"    error: ",[99,2118,849],{"class":438},[99,2120,2121],{"class":355}," as",[99,2123,875],{"class":438},[99,2125,2023],{"class":355},[99,2127,2128],{"class":438}," null\n",[99,2130,2131],{"class":101,"line":244},[99,2132,2133],{"class":359},"  }),\n",[99,2135,2136],{"class":101,"line":249},[99,2137,856],{"class":359},[99,2139,2140],{"class":101,"line":254},[99,2141,2142],{"class":359},"  getters: {\n",[99,2144,2145,2148,2151,2154,2157,2159,2161,2164,2166,2168,2170,2172],{"class":101,"line":260},[99,2146,2147],{"class":402},"    tasksByProject",[99,2149,2150],{"class":359},": (",[99,2152,2153],{"class":409},"state",[99,2155,2156],{"class":359},") ",[99,2158,2085],{"class":355},[99,2160,515],{"class":359},[99,2162,2163],{"class":409},"projectId",[99,2165,872],{"class":355},[99,2167,1986],{"class":438},[99,2169,2156],{"class":359},[99,2171,2085],{"class":355},[99,2173,427],{"class":359},[99,2175,2176,2178,2181,2184,2186,2189,2192,2195,2197],{"class":101,"line":266},[99,2177,532],{"class":355},[99,2179,2180],{"class":359}," state.tasks.",[99,2182,2183],{"class":402},"filter",[99,2185,406],{"class":359},[99,2187,2188],{"class":409},"task",[99,2190,2191],{"class":355}," =>",[99,2193,2194],{"class":359}," task.projectId ",[99,2196,1305],{"class":355},[99,2198,2199],{"class":359}," projectId);\n",[99,2201,2202],{"class":101,"line":271},[99,2203,2204],{"class":359},"    },\n",[99,2206,2207,2210,2212,2214,2216,2218,2220,2222,2224,2226,2229,2232,2235,2237],{"class":101,"line":277},[99,2208,2209],{"class":402},"    tasksByStatus",[99,2211,2150],{"class":359},[99,2213,2153],{"class":409},[99,2215,2156],{"class":359},[99,2217,2085],{"class":355},[99,2219,515],{"class":359},[99,2221,538],{"class":409},[99,2223,872],{"class":355},[99,2225,1974],{"class":402},[99,2227,2228],{"class":359},"[",[99,2230,2231],{"class":366},"'status'",[99,2233,2234],{"class":359},"]) ",[99,2236,2085],{"class":355},[99,2238,427],{"class":359},[99,2240,2241,2243,2245,2247,2249,2251,2253,2256,2258],{"class":101,"line":282},[99,2242,532],{"class":355},[99,2244,2180],{"class":359},[99,2246,2183],{"class":402},[99,2248,406],{"class":359},[99,2250,2188],{"class":409},[99,2252,2191],{"class":355},[99,2254,2255],{"class":359}," task.status ",[99,2257,1305],{"class":355},[99,2259,2260],{"class":359}," status);\n",[99,2262,2263],{"class":101,"line":288},[99,2264,563],{"class":359},[99,2266,2267],{"class":101,"line":294},[99,2268,2269],{"class":359},"  },\n",[99,2271,2272],{"class":101,"line":300},[99,2273,856],{"class":359},[99,2275,2276],{"class":101,"line":306},[99,2277,2278],{"class":359},"  actions: {\n",[99,2280,2281,2284,2286],{"class":101,"line":312},[99,2282,2283],{"class":355},"    async",[99,2285,1146],{"class":402},[99,2287,808],{"class":359},[99,2289,2290,2293,2296,2298,2301],{"class":101,"line":660},[99,2291,2292],{"class":438},"      this",[99,2294,2295],{"class":359},".loading ",[99,2297,450],{"class":355},[99,2299,2300],{"class":438}," true",[99,2302,370],{"class":359},[99,2304,2305,2307,2310,2312,2314],{"class":101,"line":675},[99,2306,2292],{"class":438},[99,2308,2309],{"class":359},".error ",[99,2311,450],{"class":355},[99,2313,1072],{"class":438},[99,2315,370],{"class":359},[99,2317,2318,2321],{"class":101,"line":691},[99,2319,2320],{"class":355},"      try",[99,2322,427],{"class":359},[99,2324,2325,2328,2330,2332,2334,2336,2338,2340],{"class":101,"line":704},[99,2326,2327],{"class":355},"        const",[99,2329,892],{"class":438},[99,2331,473],{"class":355},[99,2333,476],{"class":355},[99,2335,899],{"class":402},[99,2337,406],{"class":359},[99,2339,1165],{"class":366},[99,2341,194],{"class":359},[99,2343,2344,2347,2350,2352,2354,2356,2358],{"class":101,"line":716},[99,2345,2346],{"class":438},"        this",[99,2348,2349],{"class":359},".tasks ",[99,2351,450],{"class":355},[99,2353,476],{"class":355},[99,2355,1005],{"class":359},[99,2357,549],{"class":402},[99,2359,1010],{"class":359},[99,2361,2362,2365,2367],{"class":101,"line":721},[99,2363,2364],{"class":359},"      } ",[99,2366,748],{"class":355},[99,2368,751],{"class":359},[99,2370,2371,2373,2375,2377,2380],{"class":101,"line":726},[99,2372,2346],{"class":438},[99,2374,2309],{"class":359},[99,2376,450],{"class":355},[99,2378,2379],{"class":366}," 'Failed to fetch tasks'",[99,2381,370],{"class":359},[99,2383,2384,2386,2389],{"class":101,"line":742},[99,2385,2364],{"class":359},[99,2387,2388],{"class":355},"finally",[99,2390,427],{"class":359},[99,2392,2393,2395,2397,2399,2402],{"class":101,"line":754},[99,2394,2346],{"class":438},[99,2396,2295],{"class":359},[99,2398,450],{"class":355},[99,2400,2401],{"class":438}," false",[99,2403,370],{"class":359},[99,2405,2406],{"class":101,"line":777},[99,2407,1275],{"class":359},[99,2409,2410],{"class":101,"line":783},[99,2411,2204],{"class":359},[99,2413,2415],{"class":101,"line":2414},41,[99,2416,458],{"class":359},[99,2418,2420,2422,2425,2427,2429,2431,2434,2437,2440,2442,2445],{"class":101,"line":2419},42,[99,2421,2283],{"class":355},[99,2423,2424],{"class":402}," createTask",[99,2426,406],{"class":359},[99,2428,2188],{"class":409},[99,2430,872],{"class":355},[99,2432,2433],{"class":402}," Omit",[99,2435,2436],{"class":359},"\u003C",[99,2438,2439],{"class":402},"Task",[99,2441,413],{"class":359},[99,2443,2444],{"class":366},"'id'",[99,2446,2447],{"class":359},">) {\n",[99,2449,2451,2453],{"class":101,"line":2450},43,[99,2452,2320],{"class":355},[99,2454,427],{"class":359},[99,2456,2458,2460,2462,2464,2466,2468,2470,2472],{"class":101,"line":2457},44,[99,2459,2327],{"class":355},[99,2461,892],{"class":438},[99,2463,473],{"class":355},[99,2465,476],{"class":355},[99,2467,899],{"class":402},[99,2469,406],{"class":359},[99,2471,1165],{"class":366},[99,2473,907],{"class":359},[99,2475,2477,2480,2482],{"class":101,"line":2476},45,[99,2478,2479],{"class":359},"          method: ",[99,2481,915],{"class":366},[99,2483,493],{"class":359},[99,2485,2487,2490,2492,2494,2496],{"class":101,"line":2486},46,[99,2488,2489],{"class":359},"          headers: { ",[99,2491,925],{"class":366},[99,2493,928],{"class":359},[99,2495,931],{"class":366},[99,2497,934],{"class":359},[99,2499,2501,2504,2506,2508,2510],{"class":101,"line":2500},47,[99,2502,2503],{"class":359},"          body: ",[99,2505,942],{"class":438},[99,2507,945],{"class":359},[99,2509,948],{"class":402},[99,2511,2512],{"class":359},"(task)\n",[99,2514,2516],{"class":101,"line":2515},48,[99,2517,2518],{"class":359},"        });\n",[99,2520,2522,2524,2527,2529,2531,2533,2535],{"class":101,"line":2521},49,[99,2523,2327],{"class":355},[99,2525,2526],{"class":438}," newTask",[99,2528,473],{"class":355},[99,2530,476],{"class":355},[99,2532,1005],{"class":359},[99,2534,549],{"class":402},[99,2536,1010],{"class":359},[99,2538,2540,2542,2545,2547],{"class":101,"line":2539},50,[99,2541,2346],{"class":438},[99,2543,2544],{"class":359},".tasks.",[99,2546,1332],{"class":402},[99,2548,2549],{"class":359},"(newTask);\n",[99,2551,2553,2555,2557],{"class":101,"line":2552},51,[99,2554,2364],{"class":359},[99,2556,748],{"class":355},[99,2558,751],{"class":359},[99,2560,2562,2564,2566,2568,2571],{"class":101,"line":2561},52,[99,2563,2346],{"class":438},[99,2565,2309],{"class":359},[99,2567,450],{"class":355},[99,2569,2570],{"class":366}," 'Failed to create task'",[99,2572,370],{"class":359},[99,2574,2576],{"class":101,"line":2575},53,[99,2577,1275],{"class":359},[99,2579,2581],{"class":101,"line":2580},54,[99,2582,563],{"class":359},[99,2584,2586],{"class":101,"line":2585},55,[99,2587,780],{"class":359},[99,2589,2591],{"class":101,"line":2590},56,[99,2592,2593],{"class":359},"});\n",[10,2595,2597],{"id":2596},"lesson-6-deployment-is-a-whole-new-challenge","Lesson 6: Deployment Is a Whole New Challenge",[15,2599,2600],{},[40,2601,2602],{},"My Deployment Journey",[2604,2605,2606,2612,2618,2624],"ol",{},[37,2607,2608,2611],{},[40,2609,2610],{},"Week 1:"," \"I'll just push to GitHub, it'll be fine\"",[37,2613,2614,2617],{},[40,2615,2616],{},"Week 2:"," \"Why doesn't it work on the server?\"",[37,2619,2620,2623],{},[40,2621,2622],{},"Week 3:"," \"What do you mean environment variables?\"",[37,2625,2626,2629],{},[40,2627,2628],{},"Week 4:"," Finally got it working with Docker",[15,2631,2632],{},[40,2633,2634],{},"The Docker Setup That Worked",[89,2636,2640],{"className":2637,"code":2638,"language":2639,"meta":94,"style":94},"language-dockerfile shiki shiki-themes github-light github-dark github-dark","# Dockerfile (Backend)\nFROM node:18-alpine\n\nWORKDIR /app\n\nCOPY package*.json ./\nRUN npm ci --only=production\n\nCOPY . .\n\nEXPOSE 3000\n\nCMD [\"node\", \"server.js\"]\n","dockerfile",[96,2641,2642,2647,2652,2656,2661,2665,2670,2675,2679,2684,2688,2693,2697],{"__ignoreMap":94},[99,2643,2644],{"class":101,"line":102},[99,2645,2646],{},"# Dockerfile (Backend)\n",[99,2648,2649],{"class":101,"line":108},[99,2650,2651],{},"FROM node:18-alpine\n",[99,2653,2654],{"class":101,"line":114},[99,2655,201],{"emptyLinePlaceholder":200},[99,2657,2658],{"class":101,"line":120},[99,2659,2660],{},"WORKDIR /app\n",[99,2662,2663],{"class":101,"line":126},[99,2664,201],{"emptyLinePlaceholder":200},[99,2666,2667],{"class":101,"line":132},[99,2668,2669],{},"COPY package*.json ./\n",[99,2671,2672],{"class":101,"line":138},[99,2673,2674],{},"RUN npm ci --only=production\n",[99,2676,2677],{"class":101,"line":191},[99,2678,201],{"emptyLinePlaceholder":200},[99,2680,2681],{"class":101,"line":197},[99,2682,2683],{},"COPY . .\n",[99,2685,2686],{"class":101,"line":204},[99,2687,201],{"emptyLinePlaceholder":200},[99,2689,2690],{"class":101,"line":210},[99,2691,2692],{},"EXPOSE 3000\n",[99,2694,2695],{"class":101,"line":216},[99,2696,201],{"emptyLinePlaceholder":200},[99,2698,2699],{"class":101,"line":221},[99,2700,2701],{},"CMD [\"node\", \"server.js\"]\n",[89,2703,2707],{"className":2704,"code":2705,"language":2706,"meta":94,"style":94},"language-yaml shiki shiki-themes github-light github-dark github-dark","# docker-compose.yml\nversion: '3.8'\n\nservices:\n  backend:\n    build: ./backend\n    ports:\n      - \"3000:3000\"\n    environment:\n      - DATABASE_URL=${DATABASE_URL}\n      - JWT_SECRET=${JWT_SECRET}\n    depends_on:\n      - db\n  \n  frontend:\n    build: ./frontend\n    ports:\n      - \"4173:4173\"\n  \n  db:\n    image: postgres:15-alpine\n    environment:\n      - POSTGRES_DB=taskapp\n      - POSTGRES_PASSWORD=${DB_PASSWORD}\n    volumes:\n      - postgres_data:/var/lib/postgresql/data\n\nvolumes:\n  postgres_data:\n","yaml",[96,2708,2709,2714,2725,2729,2737,2744,2754,2761,2769,2776,2783,2790,2797,2804,2808,2815,2824,2830,2837,2841,2848,2858,2864,2871,2878,2885,2892,2896,2903],{"__ignoreMap":94},[99,2710,2711],{"class":101,"line":102},[99,2712,2713],{"class":349},"# docker-compose.yml\n",[99,2715,2716,2720,2722],{"class":101,"line":108},[99,2717,2719],{"class":2718},"sByVh","version",[99,2721,928],{"class":359},[99,2723,2724],{"class":366},"'3.8'\n",[99,2726,2727],{"class":101,"line":114},[99,2728,201],{"emptyLinePlaceholder":200},[99,2730,2731,2734],{"class":101,"line":120},[99,2732,2733],{"class":2718},"services",[99,2735,2736],{"class":359},":\n",[99,2738,2739,2742],{"class":101,"line":126},[99,2740,2741],{"class":2718},"  backend",[99,2743,2736],{"class":359},[99,2745,2746,2749,2751],{"class":101,"line":132},[99,2747,2748],{"class":2718},"    build",[99,2750,928],{"class":359},[99,2752,2753],{"class":366},"./backend\n",[99,2755,2756,2759],{"class":101,"line":138},[99,2757,2758],{"class":2718},"    ports",[99,2760,2736],{"class":359},[99,2762,2763,2766],{"class":101,"line":191},[99,2764,2765],{"class":359},"      - ",[99,2767,2768],{"class":366},"\"3000:3000\"\n",[99,2770,2771,2774],{"class":101,"line":197},[99,2772,2773],{"class":2718},"    environment",[99,2775,2736],{"class":359},[99,2777,2778,2780],{"class":101,"line":204},[99,2779,2765],{"class":359},[99,2781,2782],{"class":366},"DATABASE_URL=${DATABASE_URL}\n",[99,2784,2785,2787],{"class":101,"line":210},[99,2786,2765],{"class":359},[99,2788,2789],{"class":366},"JWT_SECRET=${JWT_SECRET}\n",[99,2791,2792,2795],{"class":101,"line":216},[99,2793,2794],{"class":2718},"    depends_on",[99,2796,2736],{"class":359},[99,2798,2799,2801],{"class":101,"line":221},[99,2800,2765],{"class":359},[99,2802,2803],{"class":366},"db\n",[99,2805,2806],{"class":101,"line":227},[99,2807,856],{"class":359},[99,2809,2810,2813],{"class":101,"line":233},[99,2811,2812],{"class":2718},"  frontend",[99,2814,2736],{"class":359},[99,2816,2817,2819,2821],{"class":101,"line":239},[99,2818,2748],{"class":2718},[99,2820,928],{"class":359},[99,2822,2823],{"class":366},"./frontend\n",[99,2825,2826,2828],{"class":101,"line":244},[99,2827,2758],{"class":2718},[99,2829,2736],{"class":359},[99,2831,2832,2834],{"class":101,"line":249},[99,2833,2765],{"class":359},[99,2835,2836],{"class":366},"\"4173:4173\"\n",[99,2838,2839],{"class":101,"line":254},[99,2840,856],{"class":359},[99,2842,2843,2846],{"class":101,"line":260},[99,2844,2845],{"class":2718},"  db",[99,2847,2736],{"class":359},[99,2849,2850,2853,2855],{"class":101,"line":266},[99,2851,2852],{"class":2718},"    image",[99,2854,928],{"class":359},[99,2856,2857],{"class":366},"postgres:15-alpine\n",[99,2859,2860,2862],{"class":101,"line":271},[99,2861,2773],{"class":2718},[99,2863,2736],{"class":359},[99,2865,2866,2868],{"class":101,"line":277},[99,2867,2765],{"class":359},[99,2869,2870],{"class":366},"POSTGRES_DB=taskapp\n",[99,2872,2873,2875],{"class":101,"line":282},[99,2874,2765],{"class":359},[99,2876,2877],{"class":366},"POSTGRES_PASSWORD=${DB_PASSWORD}\n",[99,2879,2880,2883],{"class":101,"line":288},[99,2881,2882],{"class":2718},"    volumes",[99,2884,2736],{"class":359},[99,2886,2887,2889],{"class":101,"line":294},[99,2888,2765],{"class":359},[99,2890,2891],{"class":366},"postgres_data:/var/lib/postgresql/data\n",[99,2893,2894],{"class":101,"line":300},[99,2895,201],{"emptyLinePlaceholder":200},[99,2897,2898,2901],{"class":101,"line":306},[99,2899,2900],{"class":2718},"volumes",[99,2902,2736],{"class":359},[99,2904,2905,2908],{"class":101,"line":312},[99,2906,2907],{"class":2718},"  postgres_data",[99,2909,2736],{"class":359},[10,2911,2913],{"id":2912},"what-id-do-differently","What I'd Do Differently",[15,2915,2916],{},"If I started over today, here's my approach:",[2604,2918,2919,2925,2931,2937,2943,2949],{},[37,2920,2921,2924],{},[40,2922,2923],{},"Start smaller:"," Build a simple CRUD app first",[37,2926,2927,2930],{},[40,2928,2929],{},"Use a starter template:"," Don't reinvent the wheel",[37,2932,2933,2936],{},[40,2934,2935],{},"Set up CI/CD early:"," Automate testing and deployment",[37,2938,2939,2942],{},[40,2940,2941],{},"Write tests:"," At least for critical functionality",[37,2944,2945,2948],{},[40,2946,2947],{},"Document as you go:"," Future you will thank present you",[37,2950,2951,2954],{},[40,2952,2953],{},"Ask for code reviews:"," Even from peers",[10,2956,2958],{"id":2957},"the-most-valuable-lesson","The Most Valuable Lesson",[15,2960,2961,2964],{},[40,2962,2963],{},"Building is the best teacher."," No tutorial, course, or book can replace the experience of building something real, hitting problems, and figuring out solutions.",[15,2966,2967],{},"My first full-stack project is far from perfect. The code could be cleaner, the architecture more sophisticated, and the features more polished. But it works, it's deployed, and I learned more building it than I did in months of tutorials.",[10,2969,2971],{"id":2970},"conclusion","Conclusion",[15,2973,2974],{},"Your first full-stack project will be messy. You'll make mistakes. You'll want to give up. That's normal. The key is to push through, learn from each obstacle, and celebrate small wins along the way.",[15,2976,2977,2980],{},[40,2978,2979],{},"Start building today. Your future self will thank you."," 🚀",[2982,2983,2984],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sCsY4, html code.shiki .sCsY4{--shiki-light:#6A737D;--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .so5gQ, html code.shiki .so5gQ{--shiki-light:#D73A49;--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .slsVL, html code.shiki .slsVL{--shiki-light:#24292E;--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .sfrk1, html code.shiki .sfrk1{--shiki-light:#032F62;--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html pre.shiki code .shcOC, html code.shiki .shcOC{--shiki-light:#6F42C1;--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .sQHwn, html code.shiki .sQHwn{--shiki-light:#E36209;--shiki-default:#FFAB70;--shiki-dark:#FFAB70}html pre.shiki code .suiK_, html code.shiki .suiK_{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .sByVh, html code.shiki .sByVh{--shiki-light:#22863A;--shiki-default:#85E89D;--shiki-dark:#85E89D}",{"title":94,"searchDepth":108,"depth":108,"links":2986},[2987,2990,2991,2992,2993,2994,2995,2996,2997,2998],{"id":12,"depth":108,"text":13,"children":2988},[2989],{"id":28,"depth":114,"text":29},{"id":73,"depth":108,"text":74},{"id":323,"depth":108,"text":324},{"id":1120,"depth":108,"text":1121},{"id":1481,"depth":108,"text":1482},{"id":1929,"depth":108,"text":1930},{"id":2596,"depth":108,"text":2597},{"id":2912,"depth":108,"text":2913},{"id":2957,"depth":108,"text":2958},{"id":2970,"depth":108,"text":2971},"08/10/2025","The journey of building my first full-stack application taught me invaluable lessons about development, planning, and perseverance. Here's what I learned along the way.","md",null,"/articles/launch-your-porfolio.jpg",{},"/en/articles/building-my-first-fullstack-project","8",{"title":5,"description":3000},{"loc":3005},"en/articles/building-my-first-fullstack-project",[3011,3012,3013,3014,3015],"Full-Stack","Project","Learning","Vue","Node.js","StDyUAqVxApI43jdAeiT81TnqG2ozdJTZyOTylG0SEU",{"id":3018,"extension":549,"items":3019,"meta":3082,"stem":3103,"__hash__":3104},"stack/stack.json",[3020,3024,3027,3031,3034,3038,3042,3046,3050,3054,3058,3062,3066,3070,3074,3078],{"name":3021,"link":3022,"icon":3023},"Nuxt","https://nuxt.com/","custom:nuxt",{"name":3014,"link":3025,"icon":3026},"https://vuejs.org/","custom:vue",{"name":3028,"link":3029,"icon":3030},"TypeScript","https://www.typescriptlang.org/","custom:typescript",{"name":3015,"link":3032,"icon":3033},"https://nodejs.org/","custom:node-js",{"name":3035,"link":3036,"icon":3037},"React/React Native","https://react.dev/","custom:reactjs",{"name":3039,"link":3040,"icon":3041},"TailwindCSS","https://tailwindcss.com/","custom:tailwind",{"name":3043,"link":3044,"icon":3045},"Supabase","https://www.prisma.io/","custom:supabase",{"name":3047,"link":3048,"icon":3049},"Postgre","https://www.postgresql.org/","custom:pgsql",{"name":3051,"link":3052,"icon":3053},"Rust","https://www.rust-lang.org/","custom:rust",{"name":3055,"link":3056,"icon":3057},"Python","https://www.python.org/","custom:python",{"name":3059,"link":3060,"icon":3061},"Lua","https://www.lua.org/","custom:lua",{"name":3063,"link":3064,"icon":3065},"C++","https://isocpp.org/","custom:cpp",{"name":3067,"link":3068,"icon":3069},"Golang","https://go.dev/","custom:go",{"name":3071,"link":3072,"icon":3073},"Figma","https://www.figma.com/","custom:figma",{"name":3075,"link":3076,"icon":3077},"Photoshop","https://www.adobe.com/products/photoshop.html","custom:photoshop",{"name":3079,"link":3080,"icon":3081},"Illustrator","https://www.adobe.com/products/illustrator.html","custom:illustrator",{"path":3083,"body":3084,"title":3102},"/stack",{"items":3085},[3086,3087,3088,3089,3090,3091,3092,3093,3094,3095,3096,3097,3098,3099,3100,3101],{"name":3021,"link":3022,"icon":3023},{"name":3014,"link":3025,"icon":3026},{"name":3028,"link":3029,"icon":3030},{"name":3015,"link":3032,"icon":3033},{"name":3035,"link":3036,"icon":3037},{"name":3039,"link":3040,"icon":3041},{"name":3043,"link":3044,"icon":3045},{"name":3047,"link":3048,"icon":3049},{"name":3051,"link":3052,"icon":3053},{"name":3055,"link":3056,"icon":3057},{"name":3059,"link":3060,"icon":3061},{"name":3063,"link":3064,"icon":3065},{"name":3067,"link":3068,"icon":3069},{"name":3071,"link":3072,"icon":3073},{"name":3075,"link":3076,"icon":3077},{"name":3079,"link":3080,"icon":3081},"Stack","stack","aWyFpcVGYuaHymPCKadQyiY7s0WWw_rDYWJaXBNtvoc",{"id":3106,"title":3107,"extension":549,"faqQuestions":3108,"meta":3151,"stem":3173,"subtitle":3154,"__hash__":3174},"faq_en/en/faq.json","FAQ",[3109,3121,3139],{"title":3110,"questions":3111},"Services & Skills",[3112,3115,3118],{"label":3113,"content":3114},"What services do you offer?","**Web Development**: Responsive websites, landing pages, web applications, UI implementation | **Mobile Development**: Cross-platform mobile apps (learning) | **Game Development**: Mainly writing script to FiveM (in Lua), 3D Moddeling (buildings, cars etc) | **Backend & DevOps**: API integration, database setup, deployment assistance. **I'm eager to take on projects that help me grow while delivering quality results!**",{"label":3116,"content":3117},"What technologies do you work with?","**Web**: HTML, CSS, JavaScript, TypeScript, Vue.js, Nuxt.js, React (basics), Tailwind CSS, SASS | **Mobile**: React Native (learning), Flutter (basics) | **Game Development**: C++ (basics), Lua, | **DevOps/Servers**: Git, Docker, Linux basics, Node.js, Express.js, PostgreSQL, MongoDB. **I'm always learning and expanding my skill set across different areas!**",{"label":3119,"content":3120},"Are you available for freelance work?","Yes! I'm open to freelance projects, internships, and full-time opportunities. I'm particularly interested in projects that allow me to grow my skills while delivering value.",{"title":3122,"questions":3123},"Collaboration",[3124,3127,3130,3133,3136],{"label":3125,"content":3126},"How much do you charge?","My rates are flexible and depend on the project scope and duration. For freelance work, I typically charge between €25-40 per hour. I'm also open to discussing project-based pricing for the right opportunity.",{"label":3128,"content":3129},"How does payment work for a project?","For freelance projects, I typically ask for a 40% deposit upfront to begin work, with the remaining 60% due upon completion and delivery. I accept payments via bank transfer, PayPal, or other methods we can agree upon. For larger projects, we can discuss milestone-based payments.",{"label":3131,"content":3132},"How long does a project take?","Project timelines vary depending on complexity and scope. A simple landing page might take 1-2 weeks, while a more complex web application could take 1-2 months. I always provide a realistic timeline estimate after discussing the project requirements, and I keep you updated on progress throughout.",{"label":3134,"content":3135},"How do you approach new projects?","I start by understanding the requirements, then break down the project into manageable tasks. I believe in clear communication, regular updates, and delivering clean, maintainable code. I'm not afraid to ask questions when I need clarification!",{"label":3137,"content":3138},"Do you work in teams?","Absolutely! I enjoy collaborating with other developers and designers. I'm comfortable using Git for version control, participating in code reviews, and following established coding standards.",{"title":3140,"questions":3141},"Personal",[3142,3145,3148],{"label":3143,"content":3144},"What motivates you as a developer?","I love solving problems and seeing ideas come to life through code. Every project is an opportunity to learn something new and improve my craft. The feeling of building something that people actually use is incredibly rewarding!",{"label":3146,"content":3147},"How do you stay up to date?","I follow tech blogs, participate in developer communities, work on personal projects, and take online courses. I believe continuous learning is essential in this field, and I'm always exploring new tools and best practices.",{"label":3149,"content":3150},"What are you currently learning?","I'm currently deepening my knowledge of TypeScript, exploring advanced Vue.js patterns, and learning more about performance optimization and accessibility. I'm also interested in diving deeper into backend development and DevOps.",{"path":3152,"body":3153},"/en/faq",{"title":3107,"subtitle":3154,"faqQuestions":3155},"Things I get asked a lot",[3156,3161,3168],{"title":3110,"questions":3157},[3158,3159,3160],{"label":3113,"content":3114},{"label":3116,"content":3117},{"label":3119,"content":3120},{"title":3122,"questions":3162},[3163,3164,3165,3166,3167],{"label":3125,"content":3126},{"label":3128,"content":3129},{"label":3131,"content":3132},{"label":3134,"content":3135},{"label":3137,"content":3138},{"title":3140,"questions":3169},[3170,3171,3172],{"label":3143,"content":3144},{"label":3146,"content":3147},{"label":3149,"content":3150},"en/faq","g8WlXqYfNG0mRe8GJ3xTst3K2IecqLi2uDs7IuImrEY",1785840166613]