Skip to content

Commit

Permalink
Add initial setup for NuxtHub app with API endpoints and database int…
Browse files Browse the repository at this point in the history
…egration
  • Loading branch information
CarelessCourage committed Dec 14, 2024
1 parent 9a018fc commit d7e183a
Show file tree
Hide file tree
Showing 39 changed files with 22,244 additions and 5,652 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ dist-ssr
*.njsproj
*.sln
*.sw?

.data
2 changes: 1 addition & 1 deletion .prettierrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
"tabWidth": 2,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "none"
"trailingComma": "all"
}
1 change: 1 addition & 0 deletions apps/hub/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NUXT_PUBLIC_HELLO_TEXT="Hello from the Edge 👋"
25 changes: 25 additions & 0 deletions apps/hub/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Nuxt dev/build outputs
.output
.data
.nuxt
.nitro
.cache
.wrangler
dist

# Node dependencies
node_modules

# Logs
logs
*.log

# Misc
.DS_Store
.fleet
.idea

# Local env files
.env
.env.*
!.env.example
60 changes: 60 additions & 0 deletions apps/hub/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Hello Edge

A minimal [Nuxt](https://nuxt.com) starter deployed on the Edge using [NuxtHub](https://hub.nuxt.com).

https://hello.nuxt.dev

<a href="https://hello.nuxt.dev">
<img src="https://github.com/nuxt-hub/hello-edge/assets/904724/99d1bd54-ef7e-4ac9-83ad-0a290f85edcf" alt="Hello World template for NuxtHub" />
</a>

## Features

- Server-Side rendering on Cloudflare Workers
- ESLint setup
- Ready to add a database, blob and KV storage
- One click deploy on 275+ locations for free

## Setup

Make sure to install the dependencies with [pnpm](https://pnpm.io/installation#using-corepack):

```bash
pnpm install
```

You can update the main text displayed by creating a `.env`:

```bash
NUXT_PUBLIC_HELLO_TEXT="Hello my world!"
```

## Development Server

Start the development server on `http://localhost:3000`:

```bash
pnpm dev
```

## Production

Build the application for production:

```bash
pnpm build
```

## Deploy


Deploy the application on the Edge with [NuxtHub](https://hub.nuxt.com) on your Cloudflare account:

```bash
npx nuxthub deploy
```

Then checkout your server logs, analaytics and more in the [NuxtHub Admin](https://admin.hub.nuxt.com).

You can also deploy using [Cloudflare Pages CI](https://hub.nuxt.com/docs/getting-started/deploy#cloudflare-pages-ci).

12 changes: 12 additions & 0 deletions apps/hub/app/app.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script setup lang="ts">
useSeoMeta({
title: 'NuxtHub Starter',
description: 'A Nuxt template to build your full-stack application on the edge.',
})
</script>

<template>
<NuxtRouteAnnouncer />
<NuxtLoadingIndicator />
<NuxtPage />
</template>
5 changes: 5 additions & 0 deletions apps/hub/app/auth-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { createAuthClient } from 'better-auth/vue'

export const authClient = createAuthClient({
baseURL: 'http://localhost:3000', // the base url of your auth server
})
47 changes: 47 additions & 0 deletions apps/hub/app/components/ImageGallery.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<script setup lang="ts">
const { data: images, refresh } = await useFetch('/api/images')
async function uploadImage(e: Event) {
// https://hub.nuxt.com/docs/storage/blob#useupload
const upload = useUpload('/api/images/upload', {
multiple: false,
})
const form = e.target as HTMLFormElement
await upload(form.image)
.then(async () => {
form.reset()
await refresh()
})
.catch((err) => alert('Failed to upload image:\n'+ err.data?.message))
}
async function deleteImage(pathname: string) {
await $fetch(`/api/images/${pathname}`, { method: 'DELETE' })
await refresh()
}
</script>

<template>
<div>
<h3>Images</h3>
<form @submit.prevent="uploadImage">
<label>Upload an image: <input type="file" name="image" accept="image/jpg,image/png"></label>
<button type="submit">
Upload
</button>
</form>
<p>
<img
v-for="image of images"
:key="image.pathname"
width="200"
:src="`/images/${image.pathname}`"
:alt="image.pathname"
@dblclick="deleteImage(image.pathname)"
>
</p>
<p v-if="images?.length">
<i>Tip: delete an image by double-clicking on it.</i>
</p>
</div>
</template>
34 changes: 34 additions & 0 deletions apps/hub/app/components/MessagesPanel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<script setup lang="ts">
const { data: messages, refresh } = await useFetch('/api/messages')
const newMessage = ref('')
async function sendMessage() {
if (!newMessage.value.trim()) return
await $fetch('/api/messages', {
method: 'POST',
body: {
text: newMessage.value,
},
})
newMessage.value = ''
await refresh()
}
</script>

<template>
<div>
<h3>Messages</h3>
<form @submit.prevent="sendMessage">
<input v-model="newMessage" placeholder="Type a message">
<button type="submit">
Send
</button>
</form>
<p v-for="message of messages" :key="message.id">
{{ message.text }} - {{ new Date(message.created_at).toLocaleString('fr-FR') }}
</p>
<p v-if="!messages?.length">
No messages yet
</p>
</div>
</template>
31 changes: 31 additions & 0 deletions apps/hub/app/components/RedirectsPanel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<script setup lang="ts">
const { data: redirects, refresh } = await useFetch('/api/redirects', {
transform: (data: { [key: string]: string }) => {
// Transform to text for the textarea
return { text: Object.entries(data).map(([from, to]) => `${from} ${to}`).join('\n') }
}
})
async function updateRedirects () {
const body = Object.fromEntries(
redirects.value!.text.split('\n').map(line => line.split(' '))
)
await $fetch('/api/redirects', {
method: 'PUT',
body
})
await refresh()
}
</script>

<template>
<div>
<h3>Server redirects</h3>
<form @submit.prevent="updateRedirects">
<p><textarea v-model="redirects.text" rows="6" placeholder="/from /to (one redirect per line)" style="width: 300px;" /></p>
<button type="submit">
Save redirects
</button>
</form>
</div>
</template>
50 changes: 50 additions & 0 deletions apps/hub/app/pages/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<script setup lang="ts">
const runtimeConfig = useRuntimeConfig()
const colors = ['#f87171', '#fb923c', '#fbbf24', '#facc15', '#a3e635', '#4ade80', '#34d399', '#2dd4bf', '#22d3ee', '#38bdf8', '#60a5fa', '#818cf8', '#a78bfa', '#c084fc', '#e879f9', '#f472b6', '#fb7185']
const color = useState('color', () => colors[Math.floor(Math.random() * colors.length)])
</script>

<template>
<div class="centered">
<h1 :style="{ color }">
{{ runtimeConfig.public.helloText }}
</h1>
<NuxtLink to="/" external>
refresh
</NuxtLink>
<main>
<ImageGallery />
<RedirectsPanel />
<MessagesPanel />
</main>
</div>
</template>

<style scoped>
.centered {
position: absolute;
width: 100%;
text-align: center;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
margin: 0;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}
h1 {
font-size: 32px;
}
@media (min-width: 768px) {
h1 {
font-size: 64px;
}
}
a {
color: #888;
text-decoration: none;
font-size: 18px;
}
a:hover {
text-decoration: underline;
}
</style>
7 changes: 7 additions & 0 deletions apps/hub/drizzle.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineConfig } from 'drizzle-kit'

export default defineConfig({
dialect: 'sqlite',
schema: 'server/database/schema.ts',
out: 'server/database/migrations',
})
9 changes: 9 additions & 0 deletions apps/hub/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// @ts-check
import withNuxt from './.nuxt/eslint.config.mjs'

export default withNuxt(
// Your custom configs here

).overrideRules({
'vue/max-attributes-per-line': ['warn', { singleline: 3 }],
})
36 changes: 36 additions & 0 deletions apps/hub/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
// https://nuxt.com/modules
modules: ['@nuxthub/core', '@nuxt/eslint'],

// https://devtools.nuxt.com
devtools: { enabled: true },

// Env variables - https://nuxt.com/docs/getting-started/configuration#environment-variables-and-private-tokens
runtimeConfig: {
public: {
// Can be overridden by NUXT_PUBLIC_HELLO_TEXT environment variable
helloText: 'Hello from the Edge 👋',
},
},
// https://nuxt.com/docs/getting-started/upgrade#testing-nuxt-4
future: { compatibilityVersion: 4 },
compatibilityDate: '2024-07-30',

// https://hub.nuxt.com/docs/getting-started/installation#options
hub: {
database: true,
kv: true,
blob: true,
cache: true,
},

// https://eslint.nuxt.com
eslint: {
config: {
stylistic: {
quotes: 'single',
},
},
},
})
34 changes: 34 additions & 0 deletions apps/hub/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "nuxt-app",
"private": true,
"type": "module",
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "npx nuxthub preview",
"deploy": "npx nuxthub deploy",
"postinstall": "nuxt prepare",
"lint": "eslint .",
"db:generate": "drizzle-kit generate"
},
"dependencies": {
"@nuxt/eslint": "^0.6.1",
"@nuxthub/core": "^0.8.5",
"better-auth": "^1.0.19",
"drizzle-orm": "^0.36.4",
"nuxt": "^3.14.0",
"vue": "^3.5.12",
"vue-router": "^4.4.5",
"sass": "^1.81.0"
},
"devDependencies": {
"@nuxt/eslint-config": "^0.6.1",
"drizzle-kit": "^0.28.1",
"eslint": "^9.14.0",
"typescript": "^5.6.3",
"vue-tsc": "^2.1.10",
"wrangler": "^3.84.1"
},
"packageManager": "[email protected]"
}
Loading

0 comments on commit d7e183a

Please sign in to comment.