I am developping an application with Ruby on Rails for the backend and Vuejs for the frontend. Rails is being consumed as an api. Vite is the frontend server. When I launch my application,
rails s (in one tab of my console)
vite dev (in the other)
I can only see the json provided by my controller but I can't see any frontend being rendered. It looks like the backend and the frontend are not connected properly. I have provided what I think the most importants files are to solve my issue. I don't really know how to solve this problem.
My_Application
|-app
|-controllers
|-application_controller
|-models
.
.
.
|-frontend
|-components
|-entrypoints
|-application.js
|-router.js
.
.
.
|-index.html
|-config
|-routes.rb
|-application.rb
|-vite.json
.
.
.
This is application_controller.rb
class ApplicationController < ActionController::API
before_action :configure_permitted_parameters, if: :devise_controller?
before_action :authenticate_user!
# skip_before_action :verify_authenticity_token if Rails.env.development?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up) do |u|
u.permit(:first_name, :last_name, :name, :email, :password)
end
devise_parameter_sanitizer.permit(:account_update) do |u|
u.permit(:first_name, :last_name, :name, :email, :password, :password_confirmation, :current_password)
end
end
end
This is index.html
<!DOCTYPE html>
<html>
<head>
<title>Rails7VueVite</title>
<meta charset="UTF-8">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Kode+Mono:[email protected]&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Barriecito&family=Kode+Mono:[email protected]&display=swap" rel="stylesheet">
</head>
<body>
<div class="flex flex-col h-screen">
<main class="flex flex-col">
<div id="app" class="font-sans"><div>
</main>
</div>
<script type="module" src="/entrypoints/application.js"></script>
</body>
</html>
This is application.js
// application.js
import { createApp } from 'vue'
import { createPinia } from "pinia"
import App from '../components/pages/App.vue'
import router from './router.js'
import useSessionStore from "../stores/SessionStore.js"
import '@mapbox/mapbox-gl-geocoder/dist/mapbox-gl-geocoder.css'
import './main.scss';
const app = createApp(App)
const pinia = createPinia()
// Load JWT from local storage on refresh
const loadAuthToken = async () => {
const authToken = localStorage.getItem("authToken")
const authTokenExists = authToken !== "undefined" && authToken !== null
if (authTokenExists) {
await useSessionStore(pinia).loginUserWithToken(authToken)
}
}
loadAuthToken().then(() => {
app
.use(router)
.use(pinia)
.mount("#app")
})
app.config.errorHandler = (err, instance, info) => {
console.log('wrong !!! err =>', err)
console.log('wrong !!! instance =>', instance)
console.log('wrong !!! info =>', info)
}
if (import.meta.env.DEV) {
// Enable Vue Devtools in development environment
app.config.devtools = true;
}
This is router.js
// app/javascript/packs/router.js
import { createRouter, createWebHistory } from 'vue-router'
import useSessionStore from "../stores/SessionStore"
import Home from '../components/pages/Home.vue'
import About from '../components/pages/About.vue'
import Track from '../components/pages/Track.vue'
import NewProject from '../components/pages/NewProject.vue'
import MyTracks from '../components/pages/MyTracks.vue'
import NewResultTrack from '../components/pages/NewResultTrack.vue'
import TrackConversation from '../components/conversations/TrackConversation.vue'
import Messaging from '../components/pages/Messaging.vue'
import UserForm from '../components/UserForm.vue'
const authGuard = (to, next) => {
const isLoggedIn = useSessionStore().isLoggedIn
const requiresAuth = to.meta.requiresAuth
if (isLoggedIn && !requiresAuth) return next({ name: "Home" })
if (!isLoggedIn && requiresAuth) return next({ name: "Login" })
return next()
}
const routes = [
{ path: '/', name: 'Home', component: Home },
{ path: '/aboutkkk', component: About },
{ path: '/track/:zeTrackId', name: 'track', component: Track },
{ path: '/new_project', component: NewProject },
{ path: '/my_own_tracks', component: MyTracks },
{ path: '/upload_track/:zeTrackId', name: 'result_track', component: NewResultTrack },
{ path: '/conversation/:conversationId', name: 'conversation', component: TrackConversation },
{ path: '/my_messages', component: Messaging },
{ path: '/login-signin', component: UserForm },
{ path: "/users/sign_in", name: "Login", component: UserForm, meta: { requiresAuth: false }},
{ path: "/users/sign_up",name: "Signup", component: UserForm, meta: { requiresAuth: false }},
{ path: "/users/logout",name: "Logout",meta: { requiresAuth: true },
beforeEnter: async (_to, _from, next) => {
await useSessionStore().logout()
next({ name: "Login" })
}
}
];
const router = createRouter({
history: createWebHistory(),
routes
})
router.beforeEach((to, _from, next) => {
authGuard(to, next)
})
export default router
This is routes.rb
Rails.application.routes.draw do
devise_for :users,
controllers: {
sessions: "users/sessions",
registrations: "users/registrations"
}
namespace :api do
namespace :v1 do
resources :chatrooms, only: %i[index] do
resources :messages, only: %i[index create]
end
end
end
resources :tracks
resources :instruments, only: [:index, :new, :create]
resources :genres, only: [:index, :new, :create]
root 'tracks#index'
get '/index_results/:id', to: 'tracks#index_results', as: 'index_results'
get '/my_tracks', to: 'tracks#myTracks', as: 'myTracks'
get "/member-data", to: "members#show"
get "*path", to: "static#index", constraints: proc { |request| !request.xhr? && request.format.html? }
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end
This is application.rb
require_relative "boot"
require "rails/all"
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)
module Collaborasound
class Application < Rails::Application
config.application_name = Rails.application.class.module_parent_name
# Initialize configuration defaults for originally generated Rails version.
config.load_defaults 6.1
config.session_store :cookie_store, key: "_interslice_session"
# Required for all session management (regarless of session_store)
config.middleware.use ActionDispatch::Cookies
config.middleware.use config.session_store, config.session_options
# Configuration for the application, engines, and railties goes here.
#
# These settings can be overridden in specific environments using the files
# in config/environments, which are processed later.
#
# config.time_zone = "Central Time (US & Canada)"
# config.eager_load_paths << Rails.root.join("extras")
# Only loads a smaller set of middleware suitable for API only apps.
# Middleware like session, flash, cookies can be added back manually.
# Skip views, helpers and assets when generating a new resource.
config.api_only = true
end
end
This is vite.json
{
"all": {
"sourceCodeDir": "app/frontend",
"watchAdditionalPaths": []
},
"development": {
"autoBuild": true,
"publicOutputDir": "vite-dev",
"port": 3036
},
"test": {
"autoBuild": true,
"publicOutputDir": "vite-test",
"port": 3037
}
}
This is vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import * as path from "path";
import FullReload from "vite-plugin-full-reload";
import RubyPlugin from "vite-plugin-ruby"
// import path from "path";
export default defineConfig({
plugins: [
vue(),
RubyPlugin(),
FullReload(["config/routes.rb", "app/views/**/*"], { delay: 250 })
],
resolve: {
alias: [
{
find: "@/lib",
replacement: path.resolve(__dirname, "./app/frontend/components/lib/")
},
{
find: "@/components",
replacement: path.resolve(__dirname, "./app/frontend/components/")
},
{
find: '@/entrypoints',
replacement: path.resolve(__dirname, "./app/frontend/entrypoints")
}
]
}
})
Of the code you posted, I don't see anything that's actually communicating with your Rails API.
Keep in mind that your Vue
router.jsand your Railsroutes.rbdo not automatically talk to each other. Vue is a single-page application framework, and Vue routes are what steer your user to your different Vue components. It will not talk to the Rails API for you.I see that you're using Pinia for your store. This is where you'd define your API calls. I recommend you review the Pinia docs, which includes some examples on how to define actions that communicate with an API. A basic example might look like: