├── .gitignore ├── README.md ├── knexfile.js ├── now.json ├── package.json ├── pages ├── api │ ├── companies.js │ └── jobs.js ├── companies │ └── [id].js ├── index.js └── jobs │ └── [id].js ├── public ├── favicon.ico └── zeit.svg ├── src └── db.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | !public/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | .env* 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # Environment Variables 29 | .env 30 | .env.build 31 | 32 | .now -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Next.js Hooks Database Demo 2 | 3 | Video: https://youtu.be/QABkof8ygzI 4 | 5 | Fetch your props directly from the Database?!?! Adam Wathan showed a simple example of loading props directly from the database: https://twitter.com/adamwathan/status/1246144545361997829 6 | 7 | In this video we'll explore this hook, along with two others for building static pages. Next.js 9.3 came out with 3 new hooks that allow you to fetch data in different ways: 8 | 9 | - getServerSideProps: Fetch data server side 10 | - getStaticPaths: Fetch data to generate static pages 11 | - getStaticProps: Fetch data to build a static page 12 | 13 | ## Links 14 | 15 | - Source code: https://github.com/leighhalliday/nextjs-hooks-database 16 | - Deployed app: https://next-hooks-database.now.sh/ 17 | - Full Stack Radio episode about Next.js: http://www.fullstackradio.com/137 18 | - Release notes for Next.js 9.3: https://nextjs.org/blog/next-9-3 19 | - Learn JavaScript, React & more with Treehouse (affiliate link): https://treehouse.7eer.net/c/1214671/228915/3944 20 | -------------------------------------------------------------------------------- /knexfile.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | development: { 3 | client: "postgresql", 4 | connection: process.env.PG_CONNECTION_STRING, 5 | migrations: { 6 | tableName: "knex_migrations", 7 | }, 8 | }, 9 | 10 | production: { 11 | client: "postgresql", 12 | connection: process.env.PG_CONNECTION_STRING, 13 | migrations: { 14 | tableName: "knex_migrations", 15 | }, 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /now.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "PG_CONNECTION_HOOKS_DEMO": "@pg-connection-hooks-demo" 4 | }, 5 | "build": { 6 | "env": { 7 | "PG_CONNECTION_HOOKS_DEMO": "@pg-connection-hooks-demo" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start" 9 | }, 10 | "dependencies": { 11 | "death": "^1.1.0", 12 | "knex": "^0.20.15", 13 | "next": "^9.3.3", 14 | "pg": "^8.0.2", 15 | "react": "^16.13.0", 16 | "react-dom": "^16.13.0", 17 | "swr": "^0.2.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /pages/api/companies.js: -------------------------------------------------------------------------------- 1 | import { db } from "../../src/db"; 2 | 3 | export default async (req, res) => { 4 | const companies = await db.select("*").from("companies").limit(50); 5 | 6 | res.statusCode = 200; 7 | res.setHeader("Content-Type", "application/json"); 8 | res.end(JSON.stringify(companies)); 9 | }; 10 | -------------------------------------------------------------------------------- /pages/api/jobs.js: -------------------------------------------------------------------------------- 1 | import { db } from "../../src/db"; 2 | 3 | export default async (req, res) => { 4 | let query = db.select("*").from("jobs"); 5 | if (req.query.company_id) { 6 | query = query.where({ company_id: req.query.company_id }); 7 | } 8 | const jobs = await query.limit(50); 9 | 10 | const companyIds = [...new Set(jobs.map(({ company_id }) => company_id))]; 11 | const companies = await db 12 | .select("*") 13 | .from("companies") 14 | .whereIn("id", companyIds); 15 | const companyDict = companies.reduce((acc, company) => { 16 | return { ...acc, [company.id]: company }; 17 | }, {}); 18 | 19 | const data = jobs.map((job) => { 20 | return { 21 | ...job, 22 | company: companyDict[job.company_id], 23 | }; 24 | }); 25 | 26 | res.statusCode = 200; 27 | res.setHeader("Content-Type", "application/json"); 28 | res.end(JSON.stringify(data)); 29 | }; 30 | -------------------------------------------------------------------------------- /pages/companies/[id].js: -------------------------------------------------------------------------------- 1 | import { useRouter } from "next/router"; 2 | import useSWR from "swr"; 3 | import { db } from "../../src/db"; 4 | 5 | export default function Company({ company }) { 6 | const router = useRouter(); 7 | 8 | if (router.isFallback) { 9 | return
Loading company...
; 10 | } 11 | 12 | return ( 13 |
14 |

Jobs @ {company.name}

15 |

16 | {company.url} 17 |

18 |

{company.about}

19 | 20 |
21 | ); 22 | } 23 | 24 | const fetcher = (...args) => fetch(...args).then((res) => res.json()); 25 | 26 | function Jobs({ id }) { 27 | const { data: jobs, error } = useSWR(`/api/jobs?company_id=${id}`, fetcher); 28 | 29 | if (!jobs || error) return null; 30 | 31 | return ( 32 | 37 | ); 38 | } 39 | 40 | export async function getStaticPaths() { 41 | const companies = await db 42 | .select("id") 43 | .from("companies") 44 | .where({ featured: true }); 45 | 46 | const paths = companies.map(({ id }) => { 47 | return { 48 | params: { id: id.toString() }, 49 | }; 50 | }); 51 | 52 | return { 53 | paths, 54 | fallback: true, 55 | }; 56 | } 57 | 58 | export async function getStaticProps({ params }) { 59 | const company = await db 60 | .select("*") 61 | .from("companies") 62 | .where({ id: params.id }) 63 | .first(); 64 | 65 | return { 66 | props: { company }, 67 | }; 68 | } 69 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import Link from "next/link"; 3 | import useSWR from "swr"; 4 | 5 | const fetcher = (...args) => fetch(...args).then((res) => res.json()); 6 | 7 | const Home = () => { 8 | const { data: jobs } = useSWR("/api/jobs", fetcher); 9 | 10 | return ( 11 |
12 | 13 | Jobs Jobs Jobs 14 | 15 | 16 | 17 |

Jobs Jobs Jobs

18 | 19 | {jobs ? ( 20 | 33 | ) : ( 34 |
Loading jobs...
35 | )} 36 |
37 | ); 38 | }; 39 | 40 | export default Home; 41 | -------------------------------------------------------------------------------- /pages/jobs/[id].js: -------------------------------------------------------------------------------- 1 | import { db } from "../../src/db"; 2 | 3 | export default function Job({ job, company }) { 4 | return ( 5 |
6 |

7 | {job.title} @ {company.name} 8 |

9 |
10 | ); 11 | } 12 | 13 | export async function getServerSideProps({ params, res }) { 14 | const job = await db 15 | .select("*") 16 | .from("jobs") 17 | .where({ id: params.id }) 18 | .first(); 19 | 20 | if (!job) { 21 | res.writeHead(302, { Location: "/" }); 22 | res.end(); 23 | return; 24 | } 25 | 26 | const company = await db 27 | .select("*") 28 | .from("companies") 29 | .where({ id: job.company_id }) 30 | .first(); 31 | 32 | return { 33 | props: { job, company }, 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leighhalliday/nextjs-hooks-database/3ee46bf5bf0bc282d5200b7ce113aed8b73800c3/public/favicon.ico -------------------------------------------------------------------------------- /public/zeit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/db.js: -------------------------------------------------------------------------------- 1 | import knex from "knex"; 2 | import onDeath from "death"; 3 | 4 | const db = knex({ 5 | client: "pg", 6 | connection: process.env.PG_CONNECTION_HOOKS_DEMO, 7 | debug: false, 8 | pool: { min: 1, max: 5 }, 9 | }); 10 | 11 | // Try to catch node shutting down and explicitly close 12 | // connection to database 13 | onDeath(() => { 14 | db.destroy(); 15 | }); 16 | 17 | export { db }; 18 | --------------------------------------------------------------------------------