[{"data":1,"prerenderedAt":3175},["ShallowReactive",2],{"/pl/articles/building-my-first-fullstack-project":3,"seo-stack":3017,"seo-faq-data-pl":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_pl/pl/articles/building-my-first-fullstack-project.md","Budowanie mojego pierwszego Full-Stack projektu - Wnioski",{"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},"wstęp","Wstęp",[15,16,17],"p",{},"Budowanie mojej pierwszej aplikacji full-stack było zarówno ekscytujące, jak i przerażające. Popełniłem niezliczone błędy, natknąłem się na wiele przeszkód i prawie kilka razy chciałem się poddać. Ale przytłumaczenie się nauczyło mnie więcej niż jakikolwiek poradnik. Oto moja szczera relacja z tej podróży.",[15,19,20],{},[21,22],"img",{"alt":23,"src":24},"preview","/articles/first-fullstack-project.jpg",[26,27,29],"h3",{"id":28},"projekt-aplikacja-do-zarządzania-zadaniami","Projekt: Aplikacja do zarządzania zadaniami",[15,31,32],{},"Postanowiłem zbudować wspólną aplikację do zarządzania zadaniami - ambitne jak na pierwszy projekt, wiem. Stack, który wybrałem był:",[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","Podróż budowania mojej pierwszej aplikacji full-stack nauczyła mnie bezcennych lekcji o developerskiej pracy, planowaniu i wytrwałości. Oto co się nauczyłem.","md",null,"/articles/launch-your-porfolio.jpg",{},"/pl/articles/building-my-first-fullstack-project","8",{"title":5,"description":3000},{"loc":3005},"pl/articles/building-my-first-fullstack-project",[3011,3012,3013,3014,3015],"Full-Stack","Projekt","Nauka","Vue","Node.js","d2_kNEVcPqwcGYEGKGKh-lo7YZ_xf1LlzIrr-wOBE0E",{"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_pl/pl/faq.json","FAQ",[3109,3121,3139],{"title":3110,"questions":3111},"O mnie i umiejętnościach",[3112,3115,3118],{"label":3113,"content":3114},"Jakie usługi oferujesz?","**Web Development**: Strony responsywne, landing page'i, aplikacje webowe, UI/UX | **Mobile**: Aplikacje mobilne (uczę się React Native) | **Game Dev**: Skrypty dla FiveM (Lua), Modelowanie 3D (budynki, pojazdy etc) | **Backend**: Node.js, API, bazy danych. Szukam projektów, które pomogą mi rosnąć, a klientowi dostarczę jakość.",{"label":3116,"content":3117},"Czym się zajmujesz?","**Frontend**: HTML, CSS, JavaScript, TypeScript, Vue.js, Nuxt.js, React, Tailwind | **Backend**: Node.js, Express.js, PostgreSQL, MongoDB | **DevOps**: Git, Docker, Linux, wdrażanie aplikacji. Ciągle się uczę i rozwijam swoje umiejętności.",{"label":3119,"content":3120},"Jesteś dostępny do pracy?","Tak! Szukam projektów freelance, staży i pracy pełnoetatowej. Interesują mnie projekty, które pozwolą mi się rozwijać i dostarczać wartość.",{"title":3122,"questions":3123},"Współpraca i ceny",[3124,3127,3130,3133,3136],{"label":3125,"content":3126},"Ile kosztuje?","Stawki są elastyczne - zazwyczaj €25-40 za godzinę dla pracy freelance. Dla większych projektów możemy ustalić cenę całościową lub pakietową.",{"label":3128,"content":3129},"Jak wygląda proces płatności?","Zazwyczaj proszę o 40% zaliczki na start, resztę 60% na koniec. Mogę zaakceptować przelew, PayPal albo inne metody. Dla dużych projektów - płatności etapowe po osiągnięciu milestones'ów.",{"label":3131,"content":3132},"Jak długo trwa projekt?","Zależy od skali. Prosta landing page: 1-2 tygodnie. Złożona aplikacja: 1-3 miesiące. Zawsze daję realny szacunek i informuję o postępach.",{"label":3134,"content":3135},"Jak pracujesz nad projektami?","Zaczynam od zrozumienia wymagań, potem rozbijam to na mniejsze zadania. Wierzę w jasną komunikację i czystym kod. Nie boję się pytać, gdy coś jest niejasne.",{"label":3137,"content":3138},"Pracujesz w zespołach?","Lubię! Pracowałem w zespołach, znam Git, code review, patrzę na kod innych. Staram się być łatwy do pracy i otwarty na feedback.",{"title":3140,"questions":3141},"Osobiste",[3142,3145,3148],{"label":3143,"content":3144},"Co Cię motywuje?","Rozwiązywanie problemów i widzenie, że coś moje działa w prawdziwym życiu. Każdy projekt to szansa, aby się nauczyć czegoś nowego. To satysfakcjonujące!",{"label":3146,"content":3147},"Jak się aktualizujesz?","Czytam blogi, uczestniczę w społecznościach developerów, robię side-projecty, biorę kursy. W tej branży musisz się ciągle uczyć - i lubię to.",{"label":3149,"content":3150},"Czego się uczysz teraz?","Pogłębiam TypeScript, zaawansowane wzorce Vue.js, optymalizację wydajności i accessibility. Chciałbym też więcej wiedzieć o backend'zie i DevOps'ie.",{"path":3152,"body":3153},"/pl/faq",{"title":3107,"subtitle":3154,"faqQuestions":3155},"Często zadawane pytania",[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},"pl/faq","TXjuRP2jHY72uT4DTozaL4U0aHHBGEzUGSD0KbL_qaU",1785840166623]