Skip to content

Commit 37f4ef2

Browse files
committed
chore: init
0 parents  commit 37f4ef2

23 files changed

+8482
-0
lines changed

.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
NUXT_GITHUB_CLIENT_ID=
2+
NUXT_GITHUB_CLIENT_SECRET=
3+
NUXT_SESSION_PASSWORD=

.eslintrc.cjs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
module.exports = {
2+
root: true,
3+
extends: [
4+
'@nuxt/eslint-config'
5+
],
6+
rules: {
7+
// Global
8+
semi: ['error', 'never'],
9+
quotes: ['error', 'single'],
10+
'quote-props': ['error', 'as-needed'],
11+
// Vue
12+
'vue/multi-word-component-names': 0,
13+
'vue/no-v-html': 0
14+
}
15+
}

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
node_modules
2+
*.log
3+
.nuxt
4+
nuxt.d.ts
5+
.output
6+
.env
7+
.history
8+
db.sqlite

.npmrc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
shamefully-hoist=true
2+
strict-peer-dependencies=false

README.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Todo list demo using Nuxt & CloudFlare
2+
3+
Live demo: https://nuxt-todos.pages.dev
4+
5+
- Frontend:
6+
- [Nuxt](https://nuxt.com/) - The Vue Framework for Web Architects
7+
- [TailwindCSS](https://tailwindcss.com/) for styling and layout.
8+
- Backend:
9+
- Sqlite in development and [CloudFlare D1](https://developers.cloudflare.com/d1/) in production
10+
11+
## Setup
12+
13+
Make sure to install the dependencies
14+
15+
```bash
16+
pnpm i
17+
```
18+
19+
Create a GitHub Oauth Application on https://github.com/settings/applications/new
20+
21+
With homepage url and callback url being http://localhost:3000/auth/github
22+
23+
Then, add fill the `NUXT_GITHUB_CLIENT_ID` and `NUXT_GITHUB_CLIENT_SECRET` in the `.env` file:
24+
25+
```bash
26+
NUXT_GITHUB_CLIENT_ID="my-github-oauth-app-id"
27+
NUXT_GITHUB_CLIENT_SECRET="my-github-oauth-app-secret"
28+
```
29+
30+
To create sealed session, you also need to add `NUXT_SESSION_SECRET` in the `.env` file with at least 32 characters:
31+
32+
```bash
33+
NUXT_SESSION_SECRET=your-super-long-secret-for-session-encryption
34+
```
35+
36+
## Development
37+
38+
Start the development server on http://localhost:3000
39+
40+
```bash
41+
npm run dev
42+
```
43+
44+
## Deploying on CloudFlare Pages
45+
46+
### Create your D1 database
47+
48+
- Create a D1 database on your CF account
49+
- Create a CF pages deployment linked to your GitHub repository
50+
- Add the environment variables and the ones to setup Node 18 and pnpm
51+
52+
```bash
53+
NODE_VERSION=18
54+
NPM_FLAGS=--version
55+
NUXT_GITHUB_CLIENT_ID=...
56+
NUXT_GITHUB_CLIENT_SECRET=...
57+
NUXT_SESSION_SECRET=...
58+
```
59+
60+
Set the build command to:
61+
62+
```bash
63+
npx pnpm i --store=node_modules/.pnpm-store && npm run build
64+
```
65+
66+
And the output directory to `dist/`
67+
68+
That's it :fire:

app.vue

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<script setup>
2+
const { loggedIn, user } = useUserSession()
3+
4+
useSeoMeta({
5+
title: () => user.value ? `${user.value.login} Todos` : 'Nuxt Todos Edge',
6+
description: 'A Nuxt demo hosted on CloudFlare Pages with server-side rendering on the edge and using the D1 database'
7+
})
8+
</script>
9+
10+
<template>
11+
<div class="container mx-auto min-h-screen flex items-center justify-center">
12+
<main>
13+
<div class="p-8 border rounded bg-gray-50 shadow-sm">
14+
<h1 class="text-3xl pb-4">
15+
Todo List
16+
</h1>
17+
<TodoList v-if="loggedIn" />
18+
<div v-else>
19+
<p class="text-sm text-gray-500 max-w-sm mb-4">
20+
Welcome to Nuxt Todos Edge. A demo hosted on CloudFlare Pages with server-side rendering on the edge and using the <NuxtLink
21+
href="https://developers.cloudflare.com/d1/"
22+
target="_blank"
23+
>
24+
D1 database
25+
</NuxtLink>.
26+
</p>
27+
<a
28+
href="/api/auth/github"
29+
class="inline-block bg-black text-white p-3 py-1 rounded hover:bg-gray-800"
30+
>Login with GitHub</a>
31+
<p class="text-sm text-gray-500 italic max-w-sm mt-4">
32+
No personal informations regarding your GitHub account are stored in database.<br>
33+
We store only the todos created linked with your GitHub ID.
34+
</p>
35+
</div>
36+
</div>
37+
<footer class="text-center mt-2">
38+
<NuxtLink
39+
href="https://github.com/atinux/nuxt-todos-edge"
40+
target="_blank"
41+
class="text-sm text-gray-500 hover:text-gray-700"
42+
>
43+
Source code on GitHub
44+
</NuxtLink>
45+
</footer>
46+
</main>
47+
</div>
48+
</template>

components/TodoList.vue

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<script setup lang="ts">
2+
const loading = ref(false)
3+
const newTodo = ref('')
4+
5+
const { user, clear } = useUserSession()
6+
const { data: todos } = await useFetch('/api/todos')
7+
8+
async function addTodo () {
9+
if (!newTodo.value.trim()) { return }
10+
11+
loading.value = true
12+
13+
const todo = await $fetch('/api/todos', {
14+
method: 'POST',
15+
body: {
16+
title: newTodo.value,
17+
completed: 0
18+
}
19+
})
20+
21+
todos.value!.push(todo)
22+
newTodo.value = ''
23+
loading.value = false
24+
}
25+
26+
async function toggleTodo (todo) {
27+
todo.completed = Number(!todo.completed)
28+
await useFetch(`/api/todos/${todo.id}`, {
29+
method: 'PATCH',
30+
body: {
31+
completed: todo.completed
32+
}
33+
})
34+
}
35+
36+
async function deleteTodo (todo) {
37+
await useFetch(`/api/todos/${todo.id}`, { method: 'DELETE' })
38+
todos.value = todos.value!.filter(t => t.id !== todo.id)
39+
}
40+
</script>
41+
42+
<template>
43+
<div>
44+
<p class="text-sm mb-4">
45+
Hello {{ user.login }}!
46+
<button
47+
class="underline"
48+
@click="clear"
49+
>
50+
Logout
51+
</button>
52+
</p>
53+
<form
54+
class="flex gap-2 mb-4"
55+
@submit.prevent="addTodo"
56+
>
57+
<input
58+
v-model="newTodo"
59+
type="text"
60+
:disabled="loading"
61+
class="w-full px-2 py-1 border"
62+
placeholder="Make a Nuxt demo"
63+
autocomplete="off"
64+
>
65+
<button
66+
type="submit"
67+
class="bg-black hover:bg-gray-800 text-white px-4 py-1"
68+
>
69+
Add
70+
</button>
71+
</form>
72+
<ul>
73+
<li
74+
v-for="todo of todos"
75+
:key="todo.id"
76+
class="flex items-center gap-2 py-2 border-b border-gray-200 divide-y divide-gray-200"
77+
>
78+
<span
79+
class="flex-1"
80+
:class="[todo.completed ? 'line-through u-text-gray-500' : 'u-text-gray-700']"
81+
>{{ todo.title }}</span>
82+
<input
83+
:checked="Boolean(todo.completed)"
84+
type="checkbox"
85+
:name="String(todo.id)"
86+
@change="toggleTodo(todo)"
87+
>
88+
<button
89+
type="button"
90+
class="bg-red-200 border-none text-sm rounded px-2"
91+
@click="deleteTodo(todo)"
92+
>
93+
x
94+
</button>
95+
</li>
96+
</ul>
97+
</div>
98+
</template>
99+
100+
<style lang="postcss">
101+
ul > li:last-child {
102+
@apply border-b-0;
103+
}
104+
</style>

composables/session.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { UserSession } from '~/server/utils/session'
2+
3+
const useUserSessionState = () => useState<UserSession>('nuxt-session', () => ({}))
4+
5+
export const useUserSession = () => {
6+
const sessionState = useUserSessionState()
7+
return {
8+
loggedIn: computed(() => Boolean(sessionState.value.user)),
9+
user: computed(() => sessionState.value.user || null),
10+
data: sessionState,
11+
fetch,
12+
clear
13+
}
14+
}
15+
16+
async function fetch() {
17+
useUserSessionState().value = await useRequestFetch()('/api/session').catch(_ => ({}))
18+
}
19+
20+
async function clear() {
21+
await $fetch('/api/session', { method: 'DELETE' })
22+
useUserSessionState().value = {}
23+
}

nuxt.config.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export default defineNuxtConfig({
2+
modules: [
3+
'@nuxtjs/tailwindcss'
4+
],
5+
devtools: {
6+
enabled: true
7+
},
8+
runtimeConfig: {
9+
github: {
10+
clientId: '',
11+
clientSecret: ''
12+
},
13+
session: {
14+
name: 'nuxt-session',
15+
password: ''
16+
}
17+
}
18+
})

package.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"private": true,
3+
"scripts": {
4+
"dev": "nuxi dev",
5+
"build": "nuxi build",
6+
"preview": "nuxt preview",
7+
"lint": "eslint ."
8+
},
9+
"devDependencies": {
10+
"@nuxt/devtools": "^0.4.5",
11+
"@nuxt/eslint-config": "^0.1.1",
12+
"@nuxtjs/tailwindcss": "^6.6.7",
13+
"@vercel/postgres": "^0.1.3",
14+
"better-sqlite3": "^8.3.0",
15+
"eslint": "^8.39.0",
16+
"h3-zod": "^0.5.1",
17+
"nuxt": "^3.4.3",
18+
"typescript": "^5.0.4",
19+
"zod": "^3.21.4"
20+
}
21+
}

0 commit comments

Comments
 (0)