Quickstart: Build & deploy a full-stack web app 

Estimated time: 10 minutes

Hello, and welcome to Gadget!

In this quickstart you will build an app that creates and displays custom Gadgémon (Gadget Monsters). Custom sprites for your Gadgémon will be generated using OpenAI's DALL-E API and Gadget's built-in OpenAI connection.

What you'll learn 

After finishing this quickstart, you will know how to:

Step 1: Create a new Gadget app 

Every Gadget app includes a hosted Postgres database, a serverless Node backend, and a Vite + React frontend.

  1. Start by creating a new Gadget app at https://gadget.new
  2. Select the Web app type. Make sure to use the Multi-party auth option
  3. Click the Continue button
  4. Enter your app's domain name. There's no need to change any of the default settings

Step 2: Build your model 

Models in Gadget work similarly to models in an ORM (object relational mapper) and map to tables in a database. They have one or more fields which are columns in your tables and are used to store your application data.

Create a new model to store your Gadgémon:

  1. Click the + button next to api/models to create a new model
  2. Name your model gadgemon and hit Enter on your keyboard

You can add fields to your model on the schema page at api/models/gadgemon/schema:

  1. Click the + button next to FIELDS and call the new field name (leave it as the default string type)
  2. Now add the additional fields to your Gadgémon model:
  • name: a string field that will store the name of your Gadgémon
  • similar: a string field that describes what your Gadgémon looks like
  • element: an enum field with options Grass, Fire, and Water
  • sprite: a file field that will store a generated image
A screenshot of the gadgemon model, with name, similar, element, and sprite fields. The enum field is focused, with the options set to grass, fire, and water.
More on models

That's it for building your Gadgémon model! To learn more about models, see the models documentation.

Step 3: Add the OpenAI plugin 

Gadget has built-in plugins you can use to connect your app with external services, such as OpenAI.

You can add the OpenAI plugin to your app by following these steps:

  1. Click on Settings in the sidebar
  2. Click on Plugins
  3. Select OpenAI from the list of plugins
  4. Leave the default Gadget development keys selected and click Add connection
More on plugins

You can now use the OpenAI connection in your app to generate sprites for your Gadgémon. To learn more about plugins check out the plugins documentation.

Step 4: Write your backend action 

As you build models in Gadget, a CRUD (create, read, update, delete) API is automatically generated. You can see these actions and the code that powers them in the api/models/gadgemon/actions folder.

Now add code to your gadgemon.create action that generates a sprite for your Gadgémon using the OpenAI plugin:

  1. Go to api/model/gadgemon/actions/create.js
  2. Paste the following code (replace the entire file):
api/models/gadgemon/actions/create.js
JavaScript
import { applyParams, save, ActionOptions } from "gadget-server"; import { preventCrossUserDataAccess } from "gadget-server/auth"; export const run: ActionRun = async ({ params, record, logger, api }) => { applyParams(params, record); await preventCrossUserDataAccess(params, record); await save(record); }; export const onSuccess: ActionOnSuccess = async ({ params, record, logger, api, connections, }) => { // "record" is the newly created Gadgemon, with name, similar and element fields that will be added by the user const { id, name, similar, element } = record; // prompt sent to OpenAI to generate the Gadgemon sprite const prompt = `A pixel art style pokemon sprite named ${name} that looks similar to a ${similar} that is a ${element} element. Do not include any text, including the name, in the image`; // call the OpenAI images generate (DALL-E) API: https://github.com/openai/openai-node/blob/v4/src/resources/images.ts const response = await connections.openai.images.generate({ prompt, n: 1, size: "256x256", response_format: "url", }); const imageUrl = response.data?.[0]?.url; // write to the Gadget Logs logger.info({ imageUrl }, `Generated image URL for Gadgemon id ${id}`); // save the image file to the newly created Gadgémon record await api.gadgemon.update(id, { gadgemon: { sprite: { copyURL: imageUrl, }, }, }); }; export const options: ActionOptions = { actionType: "create", timeoutMS: 60000, };
import { applyParams, save, ActionOptions } from "gadget-server"; import { preventCrossUserDataAccess } from "gadget-server/auth"; export const run: ActionRun = async ({ params, record, logger, api }) => { applyParams(params, record); await preventCrossUserDataAccess(params, record); await save(record); }; export const onSuccess: ActionOnSuccess = async ({ params, record, logger, api, connections, }) => { // "record" is the newly created Gadgemon, with name, similar and element fields that will be added by the user const { id, name, similar, element } = record; // prompt sent to OpenAI to generate the Gadgemon sprite const prompt = `A pixel art style pokemon sprite named ${name} that looks similar to a ${similar} that is a ${element} element. Do not include any text, including the name, in the image`; // call the OpenAI images generate (DALL-E) API: https://github.com/openai/openai-node/blob/v4/src/resources/images.ts const response = await connections.openai.images.generate({ prompt, n: 1, size: "256x256", response_format: "url", }); const imageUrl = response.data?.[0]?.url; // write to the Gadget Logs logger.info({ imageUrl }, `Generated image URL for Gadgemon id ${id}`); // save the image file to the newly created Gadgémon record await api.gadgemon.update(id, { gadgemon: { sprite: { copyURL: imageUrl, }, }, }); }; export const options: ActionOptions = { actionType: "create", timeoutMS: 60000, };

This code will run every time your gadgemon.create API action is called.

Test your action 

Gadget apps include an API playground that can be used to test your actions.

Use the playground to run your code in the gadgemon.create action:

  1. While in api/models/gadgemon/actions/create.js, click the Run Action button.
  2. Use the playground's API client to create a Gadgémon:
call create action in playground
JavaScript
await api.gadgemon.create({ name: "Gadgetbot", similar: "robot", element: "grass", });
await api.gadgemon.create({ name: "Gadgetbot", similar: "robot", element: "grass", });
  1. Click the execute query button to run the action

You should see a success: true response in the playground, which means you created a new gadgemon record.

  1. Go to api/models/gadgemon/data to view your model records
More on actions

Actions define your application's API. To learn more about actions, see the actions guide.

Step 5: Build your frontend 

You need an interface to create and display your Gadgémon. In Gadget, your Vite + React frontend is found inside the web folder, and an API client has been set up for you in web/api.js.

You will use this client and Gadget's React hooks to call your gadgemon.create action and read gadgemon model records.

  1. Go to web/routes/signed-in.jsx and replace the contents with the following code:
web/routes/_app.signed-in.jsx
React
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Skeleton } from "@/components/ui/skeleton"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Label } from "@/components/ui/label"; import { Separator } from "@/components/ui/separator"; import { api } from "../api"; import { useFindMany } from "@gadgetinc/react"; import { AutoForm, AutoStringInput, AutoEnumInput, AutoSubmit, SubmitResultBanner } from "@/components/auto"; import { Sparkles, Type, Flame, Droplet, Leaf, Wand2 } from "lucide-react"; const getElementColor = (element: string) => { switch (element) { case 'Grass': return 'bg-green-100 text-green-800 hover:bg-green-200'; case 'Fire': return 'bg-red-100 text-red-800 hover:bg-red-200'; case 'Water': return 'bg-blue-100 text-blue-800 hover:bg-blue-200'; default: return 'bg-gray-100 text-gray-800 hover:bg-gray-200'; } }; const GadgemonSkeleton = () => ( <Card className="overflow-hidden transition-all duration-200 hover:shadow-md"> <div className="aspect-square"> <Skeleton className="h-full w-full" /> </div> <CardContent className="p-4"> <Skeleton className="h-6 w-3/4 mb-2" /> <Skeleton className="h-4 w-1/2 mb-2" /> <Skeleton className="h-4 w-full" /> </CardContent> </Card> ); export default function() { const [{ data: gadgemon, error, fetching }] = useFindMany(api.gadgemon, { select: { id: true, name: true, sprite: { url: true, }, element: true, similar: true, }, live: true, }); return ( <div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100"> {/* Hero Section */} <div className="bg-gradient-to-r from-purple-600 to-blue-600 text-white"> <div className="container mx-auto px-6 py-16"> <div className="text-center"> <h1 className="text-5xl font-bold mb-4 animate-fade-in"> Gadgemon Universe </h1> <p className="text-xl opacity-90 max-w-2xl mx-auto"> Create and discover amazing Gadgemon creatures. Build your collection and explore the elemental world of grass, fire, and water types. </p> </div> </div> </div> <div className="container mx-auto px-6 py-12 space-y-12"> {/* Creation Form Section */} <section className="relative"> <div className="absolute inset-0 bg-gradient-to-r from-purple-100 via-blue-100 to-emerald-100 rounded-3xl opacity-60 blur-sm"></div> <div className="absolute inset-0 bg-gradient-to-br from-white/80 to-white/60 rounded-3xl"></div> <div className="relative bg-white/90 backdrop-blur-sm rounded-3xl shadow-2xl border border-white/50 hover:shadow-3xl transition-all duration-500"> <CardHeader className="text-center pb-8 pt-10"> <div className="flex items-center justify-center mb-4"> <div className="p-3 bg-gradient-to-r from-purple-500 to-blue-500 rounded-full shadow-lg"> <Wand2 className="h-8 w-8 text-white" /> </div> </div> <CardTitle className="text-4xl font-bold bg-gradient-to-r from-purple-600 to-blue-600 bg-clip-text text-transparent mb-3"> Gadgemon Factory </CardTitle> <p className="text-gray-600 text-lg max-w-md mx-auto leading-relaxed"> Craft your unique Gadgemon creature with magical elements and personality </p> </CardHeader> <CardContent className="px-10 pb-10"> <AutoForm action={api.gadgemon.create} title="" successContent={ <div className="text-center py-12 space-y-4 animate-fade-in"> <div className="flex items-center justify-center mb-6"> <div className="p-4 bg-gradient-to-r from-green-400 to-emerald-500 rounded-full shadow-lg animate-bounce"> <Sparkles className="h-8 w-8 text-white" /> </div> </div> <div className="text-2xl font-bold text-green-600 mb-3"> 🎉 Gadgemon Born Successfully! </div> <p className="text-gray-600 text-lg max-w-md mx-auto leading-relaxed"> Your magnificent new Gadgemon has joined the gallery and is ready for adventure! </p> <div className="mt-8 p-4 bg-gradient-to-r from-green-50 to-emerald-50 rounded-xl border border-green-200"> <p className="text-sm text-green-700 font-medium"> Scroll down to see your creation in the gallery </p> </div> </div> } > <div className="space-y-8"> {/* Basic Information Section */} <div className="space-y-6"> <div className="flex items-center space-x-2 mb-4"> <Type className="h-5 w-5 text-purple-500" /> <h3 className="text-xl font-semibold text-gray-800">Basic Information</h3> </div> {/* Two-column layout for name and similar on larger screens */} <div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> <div className="space-y-3 group"> <div className="relative"> <AutoStringInput field="name" label="Gadgemon Name" className="w-full px-4 py-3 border-2 border-gray-200 rounded-xl focus:ring-2 focus:ring-purple-500 focus:border-purple-500 transition-all duration-300 hover:border-purple-300 bg-white/80 backdrop-blur-sm" /> </div> <p className="text-xs text-gray-500 mt-2"> Give your Gadgemon a unique and memorable name </p> </div> <div className="space-y-3 group"> <div className="relative"> <AutoStringInput field="similar" label="Similar to" className="w-full px-4 py-3 border-2 border-gray-200 rounded-xl focus:ring-2 focus:ring-purple-500 focus:border-purple-500 transition-all duration-300 hover:border-purple-300 bg-white/80 backdrop-blur-sm" /> </div> <p className="text-xs text-gray-500 mt-2"> What does your Gadgemon resemble? (e.g., "dragon", "cat", "phoenix") </p> </div> </div> </div> <Separator className="bg-gradient-to-r from-transparent via-gray-300 to-transparent" /> {/* Element Selection Section */} <div className="space-y-6"> <div className="flex items-center space-x-2 mb-4"> <Flame className="h-5 w-5 text-orange-500" /> <h3 className="text-xl font-semibold text-gray-800">Elemental Power</h3> </div> <div className="space-y-3"> <div className="grid grid-cols-1 md:grid-cols-3 gap-4 mt-4"> <div className="flex items-center space-x-3 p-3 bg-green-50 border border-green-200 rounded-lg transition-colors duration-200"> <Leaf className="h-5 w-5 text-green-600" /> <div> <p className="text-sm font-medium text-green-800">Grass</p> <p className="text-xs text-green-600">Nature's power</p> </div> </div> <div className="flex items-center space-x-3 p-3 bg-red-50 border border-red-200 rounded-lg transition-colors duration-200"> <Flame className="h-5 w-5 text-red-600" /> <div> <p className="text-sm font-medium text-red-800">Fire</p> <p className="text-xs text-red-600">Burning spirit</p> </div> </div> <div className="flex items-center space-x-3 p-3 bg-blue-50 border border-blue-200 rounded-lg transition-colors duration-200"> <Droplet className="h-5 w-5 text-blue-600" /> <div> <p className="text-sm font-medium text-blue-800">Water</p> <p className="text-xs text-blue-600">Flowing energy</p> </div> </div> </div> <div className="relative"> <AutoEnumInput field="element" label="Choose Element Type" /> </div> </div> </div> <Separator className="bg-gradient-to-r from-transparent via-gray-300 to-transparent" /> <SubmitResultBanner /> {/* Submit Section */} <div className="flex justify-center pt-4"> <AutoSubmit className="px-12 py-4 bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-700 hover:to-blue-700 text-white font-bold rounded-xl shadow-xl hover:shadow-2xl transform transition-all duration-300 hover:scale-105 focus:outline-none focus:ring-4 focus:ring-purple-300 active:scale-95 text-lg flex items-center space-x-3"> <Sparkles className="h-6 w-6" /> <span>Create My Gadgemon</span> <Wand2 className="h-6 w-6" /> </AutoSubmit> </div> </div> </AutoForm> </CardContent> </div> </section> {/* Gallery Section */} <section> <div className="text-center mb-8"> <h2 className="text-3xl font-bold text-gray-800 mb-4"> Gadgemon Gallery </h2> <p className="text-gray-600 max-w-2xl mx-auto"> Explore your collection of unique Gadgemon creatures, each with their own elemental powers and characteristics. </p> </div> {/* Error State */} {error && ( <Alert className="mb-6 border-red-200 bg-red-50"> <AlertDescription className="text-red-700"> Failed to load Gadgemon: {error.message} </AlertDescription> </Alert> )} {/* Loading State */} {fetching && !gadgemon && ( <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> {Array.from({ length: 6 }).map((_, index) => ( <GadgemonSkeleton key={index} /> ))} </div> )} {/* Empty State */} {!fetching && !error && (!gadgemon || gadgemon.length === 0) && ( <div className="text-center py-16"> <div className="text-6xl mb-6">🌱</div> <h3 className="text-2xl font-semibold text-gray-700 mb-2"> No Gadgemon Yet </h3> <p className="text-gray-500 mb-6 max-w-md mx-auto"> Your gallery is empty. Create your first Gadgemon using the factory above to get started on your collection! </p> </div> )} {/* Gallery Grid */} {!fetching && !error && gadgemon && gadgemon.length > 0 && ( <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> {gadgemon.map(({ id, name, sprite, element, similar }) => ( <Card key={id} className="overflow-hidden transition-all duration-300 hover:shadow-xl hover:scale-105 group cursor-pointer border-0 shadow-md" > <div className="aspect-square overflow-hidden bg-gradient-to-br from-gray-50 to-gray-100"> {sprite?.url ? ( <img src={sprite.url} alt={name || "Gadgemon"} className="w-full h-full object-cover transition-transform duration-300 group-hover:scale-110" /> ) : ( <div className="w-full h-full flex items-center justify-center bg-gray-200"> <div className="text-4xl text-gray-400">🎭</div> </div> )} </div> <CardContent className="p-6"> <div className="flex items-start justify-between mb-3"> <h3 className="text-xl font-bold text-gray-800 truncate flex-1"> {name || "Unnamed Gadgemon"} </h3> <Badge className={`ml-2 transition-colors duration-200 ${getElementColor(element)}`} > {element} </Badge> </div> <p className="text-gray-600 text-sm leading-relaxed"> The <span className="font-medium">"{element} {similar}"</span> Gadgémon </p> </CardContent> </Card> ))} </div> )} </section> </div> </div> ); }
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Skeleton } from "@/components/ui/skeleton"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Label } from "@/components/ui/label"; import { Separator } from "@/components/ui/separator"; import { api } from "../api"; import { useFindMany } from "@gadgetinc/react"; import { AutoForm, AutoStringInput, AutoEnumInput, AutoSubmit, SubmitResultBanner } from "@/components/auto"; import { Sparkles, Type, Flame, Droplet, Leaf, Wand2 } from "lucide-react"; const getElementColor = (element: string) => { switch (element) { case 'Grass': return 'bg-green-100 text-green-800 hover:bg-green-200'; case 'Fire': return 'bg-red-100 text-red-800 hover:bg-red-200'; case 'Water': return 'bg-blue-100 text-blue-800 hover:bg-blue-200'; default: return 'bg-gray-100 text-gray-800 hover:bg-gray-200'; } }; const GadgemonSkeleton = () => ( <Card className="overflow-hidden transition-all duration-200 hover:shadow-md"> <div className="aspect-square"> <Skeleton className="h-full w-full" /> </div> <CardContent className="p-4"> <Skeleton className="h-6 w-3/4 mb-2" /> <Skeleton className="h-4 w-1/2 mb-2" /> <Skeleton className="h-4 w-full" /> </CardContent> </Card> ); export default function() { const [{ data: gadgemon, error, fetching }] = useFindMany(api.gadgemon, { select: { id: true, name: true, sprite: { url: true, }, element: true, similar: true, }, live: true, }); return ( <div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100"> {/* Hero Section */} <div className="bg-gradient-to-r from-purple-600 to-blue-600 text-white"> <div className="container mx-auto px-6 py-16"> <div className="text-center"> <h1 className="text-5xl font-bold mb-4 animate-fade-in"> Gadgemon Universe </h1> <p className="text-xl opacity-90 max-w-2xl mx-auto"> Create and discover amazing Gadgemon creatures. Build your collection and explore the elemental world of grass, fire, and water types. </p> </div> </div> </div> <div className="container mx-auto px-6 py-12 space-y-12"> {/* Creation Form Section */} <section className="relative"> <div className="absolute inset-0 bg-gradient-to-r from-purple-100 via-blue-100 to-emerald-100 rounded-3xl opacity-60 blur-sm"></div> <div className="absolute inset-0 bg-gradient-to-br from-white/80 to-white/60 rounded-3xl"></div> <div className="relative bg-white/90 backdrop-blur-sm rounded-3xl shadow-2xl border border-white/50 hover:shadow-3xl transition-all duration-500"> <CardHeader className="text-center pb-8 pt-10"> <div className="flex items-center justify-center mb-4"> <div className="p-3 bg-gradient-to-r from-purple-500 to-blue-500 rounded-full shadow-lg"> <Wand2 className="h-8 w-8 text-white" /> </div> </div> <CardTitle className="text-4xl font-bold bg-gradient-to-r from-purple-600 to-blue-600 bg-clip-text text-transparent mb-3"> Gadgemon Factory </CardTitle> <p className="text-gray-600 text-lg max-w-md mx-auto leading-relaxed"> Craft your unique Gadgemon creature with magical elements and personality </p> </CardHeader> <CardContent className="px-10 pb-10"> <AutoForm action={api.gadgemon.create} title="" successContent={ <div className="text-center py-12 space-y-4 animate-fade-in"> <div className="flex items-center justify-center mb-6"> <div className="p-4 bg-gradient-to-r from-green-400 to-emerald-500 rounded-full shadow-lg animate-bounce"> <Sparkles className="h-8 w-8 text-white" /> </div> </div> <div className="text-2xl font-bold text-green-600 mb-3"> 🎉 Gadgemon Born Successfully! </div> <p className="text-gray-600 text-lg max-w-md mx-auto leading-relaxed"> Your magnificent new Gadgemon has joined the gallery and is ready for adventure! </p> <div className="mt-8 p-4 bg-gradient-to-r from-green-50 to-emerald-50 rounded-xl border border-green-200"> <p className="text-sm text-green-700 font-medium"> Scroll down to see your creation in the gallery </p> </div> </div> } > <div className="space-y-8"> {/* Basic Information Section */} <div className="space-y-6"> <div className="flex items-center space-x-2 mb-4"> <Type className="h-5 w-5 text-purple-500" /> <h3 className="text-xl font-semibold text-gray-800">Basic Information</h3> </div> {/* Two-column layout for name and similar on larger screens */} <div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> <div className="space-y-3 group"> <div className="relative"> <AutoStringInput field="name" label="Gadgemon Name" className="w-full px-4 py-3 border-2 border-gray-200 rounded-xl focus:ring-2 focus:ring-purple-500 focus:border-purple-500 transition-all duration-300 hover:border-purple-300 bg-white/80 backdrop-blur-sm" /> </div> <p className="text-xs text-gray-500 mt-2"> Give your Gadgemon a unique and memorable name </p> </div> <div className="space-y-3 group"> <div className="relative"> <AutoStringInput field="similar" label="Similar to" className="w-full px-4 py-3 border-2 border-gray-200 rounded-xl focus:ring-2 focus:ring-purple-500 focus:border-purple-500 transition-all duration-300 hover:border-purple-300 bg-white/80 backdrop-blur-sm" /> </div> <p className="text-xs text-gray-500 mt-2"> What does your Gadgemon resemble? (e.g., "dragon", "cat", "phoenix") </p> </div> </div> </div> <Separator className="bg-gradient-to-r from-transparent via-gray-300 to-transparent" /> {/* Element Selection Section */} <div className="space-y-6"> <div className="flex items-center space-x-2 mb-4"> <Flame className="h-5 w-5 text-orange-500" /> <h3 className="text-xl font-semibold text-gray-800">Elemental Power</h3> </div> <div className="space-y-3"> <div className="grid grid-cols-1 md:grid-cols-3 gap-4 mt-4"> <div className="flex items-center space-x-3 p-3 bg-green-50 border border-green-200 rounded-lg transition-colors duration-200"> <Leaf className="h-5 w-5 text-green-600" /> <div> <p className="text-sm font-medium text-green-800">Grass</p> <p className="text-xs text-green-600">Nature's power</p> </div> </div> <div className="flex items-center space-x-3 p-3 bg-red-50 border border-red-200 rounded-lg transition-colors duration-200"> <Flame className="h-5 w-5 text-red-600" /> <div> <p className="text-sm font-medium text-red-800">Fire</p> <p className="text-xs text-red-600">Burning spirit</p> </div> </div> <div className="flex items-center space-x-3 p-3 bg-blue-50 border border-blue-200 rounded-lg transition-colors duration-200"> <Droplet className="h-5 w-5 text-blue-600" /> <div> <p className="text-sm font-medium text-blue-800">Water</p> <p className="text-xs text-blue-600">Flowing energy</p> </div> </div> </div> <div className="relative"> <AutoEnumInput field="element" label="Choose Element Type" /> </div> </div> </div> <Separator className="bg-gradient-to-r from-transparent via-gray-300 to-transparent" /> <SubmitResultBanner /> {/* Submit Section */} <div className="flex justify-center pt-4"> <AutoSubmit className="px-12 py-4 bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-700 hover:to-blue-700 text-white font-bold rounded-xl shadow-xl hover:shadow-2xl transform transition-all duration-300 hover:scale-105 focus:outline-none focus:ring-4 focus:ring-purple-300 active:scale-95 text-lg flex items-center space-x-3"> <Sparkles className="h-6 w-6" /> <span>Create My Gadgemon</span> <Wand2 className="h-6 w-6" /> </AutoSubmit> </div> </div> </AutoForm> </CardContent> </div> </section> {/* Gallery Section */} <section> <div className="text-center mb-8"> <h2 className="text-3xl font-bold text-gray-800 mb-4"> Gadgemon Gallery </h2> <p className="text-gray-600 max-w-2xl mx-auto"> Explore your collection of unique Gadgemon creatures, each with their own elemental powers and characteristics. </p> </div> {/* Error State */} {error && ( <Alert className="mb-6 border-red-200 bg-red-50"> <AlertDescription className="text-red-700"> Failed to load Gadgemon: {error.message} </AlertDescription> </Alert> )} {/* Loading State */} {fetching && !gadgemon && ( <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> {Array.from({ length: 6 }).map((_, index) => ( <GadgemonSkeleton key={index} /> ))} </div> )} {/* Empty State */} {!fetching && !error && (!gadgemon || gadgemon.length === 0) && ( <div className="text-center py-16"> <div className="text-6xl mb-6">🌱</div> <h3 className="text-2xl font-semibold text-gray-700 mb-2"> No Gadgemon Yet </h3> <p className="text-gray-500 mb-6 max-w-md mx-auto"> Your gallery is empty. Create your first Gadgemon using the factory above to get started on your collection! </p> </div> )} {/* Gallery Grid */} {!fetching && !error && gadgemon && gadgemon.length > 0 && ( <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> {gadgemon.map(({ id, name, sprite, element, similar }) => ( <Card key={id} className="overflow-hidden transition-all duration-300 hover:shadow-xl hover:scale-105 group cursor-pointer border-0 shadow-md" > <div className="aspect-square overflow-hidden bg-gradient-to-br from-gray-50 to-gray-100"> {sprite?.url ? ( <img src={sprite.url} alt={name || "Gadgemon"} className="w-full h-full object-cover transition-transform duration-300 group-hover:scale-110" /> ) : ( <div className="w-full h-full flex items-center justify-center bg-gray-200"> <div className="text-4xl text-gray-400">🎭</div> </div> )} </div> <CardContent className="p-6"> <div className="flex items-start justify-between mb-3"> <h3 className="text-xl font-bold text-gray-800 truncate flex-1"> {name || "Unnamed Gadgemon"} </h3> <Badge className={`ml-2 transition-colors duration-200 ${getElementColor(element)}`} > {element} </Badge> </div> <p className="text-gray-600 text-sm leading-relaxed"> The <span className="font-medium">"{element} {similar}"</span> Gadgémon </p> </CardContent> </Card> ))} </div> )} </section> </div> </div> ); }
More on frontends

Like your database and backend, your Gadget frontends are already hosted and live on the internet. For more information on frontends, read the frontend docs.

Test your app 

Now you can view your completed app (and your Gadgémon!) in the frontend:

  1. Click Preview in the top right of the Gadget editor
  2. Sign up and sign in to your app

You should see your Gadgémon displayed on the page.

Try creating new Gadgémon using the form!

A screenshot of the completed app. There are 3 Gadgemon visible in the app frontend, Gadgetbot the grass robot, Bob the fire whale, and Kaypeedee the water Honda Odyssey minivan. The form with Name, Looks similar to a ... and element fields is present, but empty.

Next steps 

Try out some of our other tutorials to learn more about building with Gadget:

Questions? 

Reach out on Gadget's Discord server to talk with Gadget employees and the Gadget developer community!

Was this page helpful?