This guide documents the complete migration of AbsG5 from legacy stack to modern technologies. Use this as a reference for understanding changes, troubleshooting issues, and maintaining the application.
| Component | Before | After | Reason |
|---|---|---|---|
| Node.js | 14.x | 20.x LTS | Security, performance, modern features |
| TypeScript | 4.x | 5.x | Better type inference, performance |
| TypeORM | 0.2.x | 0.3.x | DataSource API, better async support |
| PostgreSQL | 12.x | 16.x | Performance, new features |
| PostGIS | 2.x | 3.4.x | Spatial query improvements |
| Express | 4.17.x | 4.19.x | Security patches |
| bcrypt | 3.x | 5.1.x | Stronger hashing |
| jsonwebtoken | 8.x | 9.0.x | Security updates |
| axios | 0.x | 1.7.x | Security, stability |
| ws | 7.x | 8.18.x | WebSocket improvements |
| Component | Before | After | Reason |
|---|---|---|---|
| Vue | 2.7.x | 3.4.x | Composition API, performance |
| Vue Router | 3.x | 4.x | Vue 3 compatibility |
| Vuex | 4.x | Pinia 2.x | Modern, lightweight, TypeScript |
| Vuetify | 2.x | 3.5.x | Vue 3 support, modern design |
| Build Tool | Vue CLI | Vite 5.x | Faster builds, HMR |
| Test Framework | Jest | Vitest 4.x | Native Vite integration |
Before (0.2.x):
import { createConnection } from 'typeorm'
const connection = await createConnection({
type: 'postgres',
// ...config
})
const userRepo = connection.getRepository(User)After (0.3.x):
import { DataSource } from 'typeorm'
export const AppDataSource = new DataSource({
type: 'postgres',
// ...config
})
await AppDataSource.initialize()
const userRepo = AppDataSource.getRepository(User)Migration Steps:
- Replace
createConnectionwithDataSource - Update all
getRepository()calls to useAppDataSource.getRepository() - Update entity decorators if needed
- Test all database operations
Before:
@Column()
name: stringAfter:
@Column({ type: 'varchar' })
name: stringAction: Add explicit column types to all entity properties
Before (Options API):
<script>
export default {
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++
}
}
}
</script>After (Composition API - optional):
<script setup>
import { ref } from 'vue'
const count = ref(0)
const increment = () => count.value++
</script>Note: Options API still works in Vue 3. Migration to Composition API is optional.
Before (Vuex):
// store/index.js
export default new Vuex.Store({
state: { user: null },
mutations: {
SET_USER(state, user) {
state.user = user
}
},
actions: {
login({ commit }, credentials) {
// ...
commit('SET_USER', user)
}
}
})
// Component
this.$store.dispatch('login', credentials)
this.$store.state.userAfter (Pinia):
// stores/user.js
export const useUserStore = defineStore('user', {
state: () => ({ user: null }),
actions: {
async login(credentials) {
// ...
this.user = user
}
}
})
// Component
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
userStore.login(credentials)
userStore.userBackward Compatibility: We provide stores/helpers.js for gradual migration:
// Old code still works
import { mapState, mapActions } from '@/stores/helpers'Before:
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '*', component: NotFound }
]
})After:
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/:pathMatch(.*)*', component: NotFound }
]
})Changes:
mode: 'history'→history: createWebHistory()- Catch-all route:
*→/:pathMatch(.*)* router.push()now returns a Promise
Common Changes:
| Vuetify 2 | Vuetify 3 | Notes |
|---|---|---|
v-simple-table |
v-table |
Simplified API |
dense prop |
density="compact" |
New prop name |
v-expansion-panel-header |
v-expansion-panel-title |
Renamed |
v-expansion-panel-content |
v-expansion-panel-text |
Renamed |
text button |
variant="text" |
New variant system |
depressed button |
(default) | No longer needed |
small prop |
size="small" |
New sizing system |
left icon |
start |
Renamed for i18n |
right icon |
end |
Renamed for i18n |
Tooltip Changes:
<!-- Before -->
<v-tooltip bottom>
<template #activator="{ on }">
<v-btn v-on="on">Button</v-btn>
</template>
<span>Tooltip</span>
</v-tooltip>
<!-- After -->
<v-tooltip bottom>
<template #activator="{ props }">
<v-btn v-bind="props">Button</v-btn>
</template>
<span>Tooltip</span>
</v-tooltip>Before (vue.config.js):
module.exports = {
devServer: {
proxy: 'http://localhost:3000'
}
}After (vite.config.js):
export default defineConfig({
server: {
proxy: {
'/api': 'http://localhost:3000'
}
}
})Changes:
index.htmlmoved to project rootprocess.env.VUE_APP_*→import.meta.env.VITE_*- Faster dev server and builds
- Native ES modules
Old Pattern (Vuex):
<script>
export default {
computed: {
...mapState(['user', 'notifications']),
...mapGetters(['isLoggedIn'])
},
methods: {
...mapActions(['login', 'logout'])
}
}
</script>New Pattern (Pinia):
<script>
import { useUserStore } from '@/stores/user'
import { useNotificationStore } from '@/stores/notification'
export default {
setup() {
const userStore = useUserStore()
const notifStore = useNotificationStore()
return {
user: computed(() => userStore.currentUser),
isLoggedIn: computed(() => userStore.isLoggedIn),
login: userStore.login,
logout: userStore.logout
}
}
}
</script>Backward Compatible (using helpers):
<script>
import { mapState, mapActions } from '@/stores/helpers'
export default {
computed: {
...mapState('user', ['currentUser', 'isLoggedIn'])
},
methods: {
...mapActions('user', ['login', 'logout'])
}
}
</script>Old (Vuex):
actions: {
async fetchData({ commit }) {
const data = await api.get('/data')
commit('SET_DATA', data)
}
}New (Pinia):
actions: {
async fetchData() {
this.data = await api.get('/data')
}
}Old (vue-native-websocket):
Vue.use(VueNativeSock, 'ws://localhost:3000', {
store,
format: 'json'
})New (Custom Plugin):
// plugins/websocket.js
export default {
install(app) {
const socket = new WebSocket('ws://localhost:3000')
const wsStore = useWebSocketStore()
socket.onopen = () => wsStore.onOpen()
socket.onclose = () => wsStore.onClose()
socket.onmessage = (e) => wsStore.onMessage(JSON.parse(e.data))
app.config.globalProperties.$socket = socket
}
}Steps:
-
Backup current database:
pg_dump -U postgres absg5 > backup.sql -
Install PostgreSQL 16.x and PostGIS 3.4.x
-
Create new database:
createdb -U postgres absg5 psql -U postgres -d absg5 -c "CREATE EXTENSION postgis;" -
Restore data:
psql -U postgres absg5 < backup.sql -
Update connection string in
.env
All existing migrations work with TypeORM 0.3.x. No changes needed.
# Database
DB_HOST=localhost
DB_PORT=5432
DB_USERNAME=postgres
DB_PASSWORD=your_password
DB_DATABASE=absg5
# Server
PORT=3000
NODE_ENV=development
# Security
JWT_SECRET=your_jwt_secret
BCRYPT_ROUNDS=10
# CORS
CORS_ORIGIN=http://localhost:5173
# Files
PATH_FILES=./data/files# API
VITE_API_URL=http://localhost:3000
# WebSocket
VITE_WS_URL=ws://localhost:3000Note: Changed from VUE_APP_* to VITE_*
Backend:
cd absg-core
npm test # Watch mode
npm run test:run # Single run
npm run test:coverage # With coverageFrontend:
cd absg-client
npm test # Watch mode
npm run test:run # Single run
npm run test:coverage # With coverageabsg-core/test/
├── setup.ts # Test setup
├── example.test.ts # Example tests
├── integration/ # Integration tests
│ └── auth.test.ts
└── helpers/ # Test helpers
└── testApp.ts
absg-client/test/
├── setup.js # Test setup
├── example.test.js # Example tests
└── stores/ # Store tests
├── user.test.js
├── notification.test.js
├── photoGallery.test.js
├── agpa.test.js
└── websocket.test.js
Cause: Path aliases not configured
Solution: Check vite.config.js has correct aliases:
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}Cause: Vuetify 3 not properly configured
Solution: Check main.js has Vuetify plugin:
import { createVuetify } from 'vuetify'
import 'vuetify/styles'
const vuetify = createVuetify()
app.use(vuetify)Cause: Pinia not initialized
Solution: Check main.js has Pinia before router:
import { createPinia } from 'pinia'
const pinia = createPinia()
app.use(pinia)
app.use(router)Cause: WebSocket store not initialized Solution: Check WebSocket plugin is registered and store is imported
Cause: DataSource not initialized
Solution: Check api.ts calls AppDataSource.initialize() before using repositories
Solution: Vite should be much faster. If slow:
- Clear
node_modules/.vitecache - Check for large dependencies
- Use
npm run buildto check production bundle
Solution:
- Check indexes are present
- Use
EXPLAIN ANALYZEto profile queries - Consider adding caching (Redis)
If critical issues arise:
-
Stop services:
pm2 stop all
-
Restore database backup:
psql -U postgres absg5 < backup_pre_migration.sql -
Checkout previous version:
git checkout main npm install
-
Restart services:
pm2 start all
- All tests passing
- Application loads without errors
- Authentication works
- Photo upload/viewing works
- Forum posting works
- AGPA functionality works
- WebSocket connects
- Admin functions work
- Performance acceptable
- No console errors
- Mobile responsive
- Cross-browser tested
- Vue 3: https://vuejs.org/
- Pinia: https://pinia.vuejs.org/
- Vue Router 4: https://router.vuejs.org/
- Vuetify 3: https://vuetifyjs.com/
- Vite: https://vitejs.dev/
- TypeORM 0.3: https://typeorm.io/
README.md- Project overviewSECURITY_AUDIT.md- Security statusPERFORMANCE_TESTING.md- Performance guideMANUAL_TESTING_CHECKLIST.md- Testing checklist
This migration brings AbsG5 to modern standards with improved:
- Performance: Faster builds, better runtime performance
- Security: Updated dependencies, security headers, rate limiting
- Developer Experience: Better tooling, faster HMR, better TypeScript support
- Maintainability: Modern patterns, better testing, clearer code
The application is now ready for continued development and deployment.