In this post we will add a Camera using Capacitor Camera Plugin, add a backend with using Prisma with SQLite as our database, deploy to mobile device and run the server creating a full-stack mobile experience using VueJS Nuxt 3 and Ionic Framework
Create the database models for the application. Notice I have specified the database url directly in the file and not used an .env file.
// This is your Prisma schema file,// learn more about it in the docs: https://pris.ly/d/prisma-schemageneratorclient{provider="prisma-client-js"}datasourcedb{provider="sqlite"url="file:./mydata.db"}modelImagePost{idInt@id@default(autoincrement())createdAtDateTime@default(now())updatedAtDateTime@updatedAttitleStringcontentString?imageString?publishedBoolean@default(false)}
Command to migrate changes to database
npx prisma migrate dev --name initialize_db
Since we are using Nuxt for our backend we will create two api routes in the server/api/ directory; one for getting all of ImagePost records and another for adding them.
You will modify your runtime configuration to support the url for the api when you are running locally and developing in the browser which is development; and when testing on device/phone which is production production in this configuration.
<template><ion-page><ion-header><ion-toolbar><ion-title>Nuxt Ionic Prisma Photo Demo</ion-title><ion-buttonsslot="end"><ion-button>LOGOUT</ion-button></ion-buttons></ion-toolbar></ion-header><ion-contentclass="ion-padding"><!-- indicators --><ion-loading:is-open="pending"message="LOADING..."></ion-loading><ion-loading:is-open="saving"message="SAVING..."></ion-loading><!-- modal to get title and content before saving --><image-post-input:showImagePostInput="showImagePostInput":imageURL="imageURL"@image-post-submit="doSave"@image-post-cancel="showImagePostInput = false"/><p>
Sample app with Nuxt for server and client mobile app. Prisma for saving the data
to database and Ionic / Capacitor for device capabilities
</p><!-- click to take photo and save to database --><ion-button@click="doCamera"> CREATE IMAGE POST </ion-button><!-- loop through records in database --><ion-cardv-for="item in data":key="item?.id"><ion-card-header><ion-card-title>{{ item?.title }}</ion-card-title><ion-card-subtitle>{{ item?.content }}</ion-card-subtitle></ion-card-header><ion-card-contentv-if="item?.image"><ion-img:src="(item?.image as any)"/></ion-card-content></ion-card></ion-content></ion-page></template>
// routerconstionRouter=useIonRouter();// ref holding the image from the cameraconstimageURL=ref<string|null>(null);// flag for rendering the saving indicator ui elementconstsaving=ref<boolean>(false);// flag for rendering the ImagePostInput ModalconstshowImagePostInput=ref<boolean>(false);// api url from config to support dev and prod api callsconstAPI_URL=useRuntimeConfig().public.API_URL;
We using the pending to indicate we are loading, data is the result from the query, error is used to show an alert if there is a problem and refresh is used to reload the data after we add a new ImagePost
Alert code for when there is an error and the function we use for displaying an alert since it appears multiple times in the application
constdoAlert=(options:{header:string;message:string})=>{returnalertController.create({buttons:["OK"],...options}).then((alert)=>alert.present());};// display error if necessaryif (error?.value){doAlert({header:"Error Loading Data",message:(error?.valueasError)?.message,});}
This is the function to take the picture, it is called when button is clicked in the template. If an image is captured, we set the ref imageURL which will render the image in the input form. the last thing we do is set the boolean flag to show the modal component ImagePostInput
constdoCamera=async ()=>{constimage=awaitCamera.getPhoto({quality:90,// allowEditing: true,correctOrientation:true,width:400,resultType:CameraResultType.Base64,});imageURL.value=`data:${image.format};base64,${image.base64String}`;// show dialog to confirm imageshowImagePostInput.value=true;};
function for saving the ImagePost to the data base using the server api route we created. it is called if the submit event is emitted from the dialog
constdoSave=async ({title,content}:{title:string;content:string})=>{// hide the input formshowImagePostInput.value=false;// show the saving indicatorsaving.value=true;try{constdataToSave:Partial<ImagePost>={title,content,image:imageURL.value,published:true,};await$fetch(`${API_URL}/post`,{method:"POST",headers:{"Content-Type":"application/json",},body:JSON.stringify(dataToSave),});imageURL.value=null;// reload the data for the UIawaitrefresh();// hide saving ui elementsaving.value=false;// display alert to indicate successful savedoAlert({header:"Saving Image Post",message:"Image saved successfully",});}catch (error){saving.value=false;doAlert({header:"Error",message:error.message,});}};
ImagePostInput Component - Capture additional Data and Save To Database
Input form for capturing additional information about the photo, this is trigger to be opened after the user takes a photo with the camera
defineProps({// flag to show/hide the modalshowImagePostInput:{type:Boolean,default:false,},// image URL dataimageURL:{type:String,default:"",},});// events emmitted by the componentconstemit=defineEmits<{// event to close the modal(event:"image-post-cancel"):void;// event to save the data(event:"image-post-submit",{title,content}:{title:string;content:string}):void;}>();// data from the component formconstdata=ref({title:"",content:"",});/**
* close modal take no action
*/constcloseModal=()=>{emit("image-post-cancel");};/**
* close modal and pass form data
*/constonSubmit=()=>{emit("image-post-submit",{title:data.value.title,content:data.value.content,});};
Capacitor Configuration and Setup
$ npm install--save @capacitor/core @capacitor/cli
$ npx cap init
Add your platform that you want to use, Android or IOS
$ npx cap add android
$ npx cap add ios
Additional Configuration
Since we are using the camera we will need to install the camera component
This code should now run fine as a PWA and provide a camera for you to take a photo and save to the data base.
if you want to deploy to a mobile device you need to have the backend running on a known ip address and then make sure you set that ip address in the nuxt configuration
runtimeConfig: {
public: {
API_URL: process.env.NODE_ENV ==="development" ? "/api" : [YOUR SERVER IP ADDRESS],
}},
now you can deploy the app to you mobile device and then start up your nuxt server and you should be good to go.
Using Nuxt-Ionic: Full Stack Mobile With Prisma SQLite & Ionic Framework w/ Capacitor
#nuxt #ionic #prisma
This is a follow-up to my video on using nuxt-ionic to build and deploy a mobile application using Ionic Framework Vue Components and Capacitor for deploying to native devices.
In this post, we will add a Camera using Capacitor Camera Plugin, add a backend using Prisma with SQLite as our database, deploy it to a mobile device and run the server creating a full-stack mobile experience using VueJS Nuxt 3 and Ionic Framework