diff --git a/packages/backend/native-utils/Cargo.lock b/packages/backend/native-utils/Cargo.lock
index eb41326c..3d48b5ef 100644
--- a/packages/backend/native-utils/Cargo.lock
+++ b/packages/backend/native-utils/Cargo.lock
@@ -1431,6 +1431,7 @@ dependencies = [
  "thiserror",
  "tokio",
  "url",
+ "urlencoding",
 ]
 
 [[package]]
diff --git a/packages/backend/native-utils/Cargo.toml b/packages/backend/native-utils/Cargo.toml
index 161d1820..2f214f15 100644
--- a/packages/backend/native-utils/Cargo.toml
+++ b/packages/backend/native-utils/Cargo.toml
@@ -36,6 +36,7 @@ url = "2.5.0"
 napi = { version = "2.14.2", default-features = false, features = ["napi9", "tokio_rt", "chrono_date", "serde-json"] }
 napi-derive = { version = "2.14.6", optional = true }
 basen = "0.1.0"
+urlencoding = "2.1.3"
 
 [dev-dependencies]
 pretty_assertions = "1.4.0"
diff --git a/packages/backend/native-utils/src/database/error.rs b/packages/backend/native-utils/src/database/error.rs
index f180870f..beb9f363 100644
--- a/packages/backend/native-utils/src/database/error.rs
+++ b/packages/backend/native-utils/src/database/error.rs
@@ -11,3 +11,12 @@ pub enum Error {
 }
 
 impl_napi_error_from!(Error);
+
+pub trait NapiDbErrExt {
+    fn into(self) -> napi::Error;
+}
+impl NapiDbErrExt for DbErr {
+    fn into(self) -> napi::Error {
+        napi::Error::from_reason(self.to_string())
+    }
+}
diff --git a/packages/backend/native-utils/src/database/mod.rs b/packages/backend/native-utils/src/database/mod.rs
index 739f39bb..73d9c400 100644
--- a/packages/backend/native-utils/src/database/mod.rs
+++ b/packages/backend/native-utils/src/database/mod.rs
@@ -1,26 +1,44 @@
 pub mod error;
+pub use error::NapiDbErrExt;
 
-use error::Error;
+use crate::config::server::read_server_config;
 use sea_orm::{Database, DbConn};
+use urlencoding::encode;
 
-static DB_CONN: once_cell::sync::OnceCell<DbConn> = once_cell::sync::OnceCell::new();
-
-pub async fn init_database(conn_uri: impl Into<String>) -> Result<(), Error> {
-    let conn = Database::connect(conn_uri.into()).await?;
-    DB_CONN.get_or_init(move || conn);
-    Ok(())
+#[napi_derive::napi]
+pub struct JsDbConn {
+    inner: DbConn,
 }
-
-pub fn get_database() -> Result<&'static DbConn, Error> {
-    DB_CONN.get().ok_or(Error::Uninitialized)
-}
-
-#[cfg(test)]
-mod unit_test {
-    use super::{error::Error, get_database};
-
-    #[test]
-    fn error_uninitialized() {
-        assert_eq!(get_database().unwrap_err(), Error::Uninitialized);
+impl JsDbConn {
+    pub fn inner(&self) -> &DbConn {
+        &self.inner
     }
 }
+
+#[napi_derive::napi]
+pub async fn init_database() -> napi::Result<JsDbConn> {
+    let config = read_server_config().db;
+    let conn_uri = format!(
+        "postgres://{}:{}@{}:{}/{}",
+        config.user,
+        encode(&config.pass),
+        config.host,
+        config.port,
+        config.db,
+    );
+    let conn = Database::connect(conn_uri)
+        .await
+        .map_err(NapiDbErrExt::into)?;
+    Ok(JsDbConn { inner: conn })
+}
+
+// TODO
+// #[cfg(test)]
+// mod unit_test {
+//     use super::{error::Error, get_database};
+//
+//     #[test]
+//     fn error_uninitialized() {
+//         assert_eq!(get_database().unwrap_err(), Error::Uninitialized);
+//     }
+// }
diff --git a/packages/backend/native-utils/src/model/entity/meta.rs b/packages/backend/native-utils/src/model/entity/meta.rs
index 27a33fdd..2521d681 100644
--- a/packages/backend/native-utils/src/model/entity/meta.rs
+++ b/packages/backend/native-utils/src/model/entity/meta.rs
@@ -6,7 +6,7 @@ use sea_orm::entity::prelude::*;
 
 #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
 #[sea_orm(table_name = "meta")]
-#[napi_derive::napi(object)]
+#[napi_derive::napi(object, js_name = "Meta")]
 pub struct Model {
     #[sea_orm(primary_key, auto_increment = false)]
     pub id: String,
diff --git a/packages/backend/native-utils/src/util/fetch_meta.rs b/packages/backend/native-utils/src/util/fetch_meta.rs
new file mode 100644
index 00000000..ad0578d5
--- /dev/null
+++ b/packages/backend/native-utils/src/util/fetch_meta.rs
@@ -0,0 +1,47 @@
+use sea_orm::{prelude::*, ActiveValue};
+use std::sync::Mutex;
+// use sea_orm::{ActiveValue, prelude::*, sea_query::OnConflict};
+use crate::database::{JsDbConn, NapiDbErrExt};
+use crate::model::entity::meta;
+
+type Meta = meta::Model;
+
+static CACHE: Mutex<Option<Meta>> = Mutex::new(None);
+fn update_cache(meta: &Meta) {
+    let _ = CACHE.lock().map(|mut cache| *cache = Some(meta.clone()));
+}
+
+#[napi_derive::napi]
+pub async fn fetch_meta(conn: &JsDbConn, invalidate_cache: bool) -> napi::Result<Meta> {
+    // try using cache
+    if !invalidate_cache {
+        if let Some(cache) = CACHE.lock().ok().and_then(|cache| cache.clone()) {
+            return Ok(cache);
+        }
+    }
+
+    // try fetching from db
+    let db = conn.inner();
+    let meta = meta::Entity::find()
+        .one(db)
+        .await
+        .map_err(NapiDbErrExt::into)?;
+    if let Some(meta) = meta {
+        update_cache(&meta);
+        return Ok(meta);
+    }
+
+    // create a new meta object and insert into db
+    let meta = meta::Entity::insert(meta::ActiveModel {
+        id: ActiveValue::Set("x".to_owned()),
+        ..Default::default()
+    })
+    // FIXME handle on conflict
+    // .on_conflict(OnConflict::column(meta::Column::Id).do_nothing().to_owned())
+    // .do_nothing()
+    .exec_with_returning(db)
+    .await
+    .map_err(NapiDbErrExt::into)?;
+    update_cache(&meta);
+    Ok(meta)
+}
diff --git a/packages/backend/native-utils/src/util/mod.rs b/packages/backend/native-utils/src/util/mod.rs
index 79c10c95..fb695098 100644
--- a/packages/backend/native-utils/src/util/mod.rs
+++ b/packages/backend/native-utils/src/util/mod.rs
@@ -2,6 +2,7 @@ pub mod acct;
 pub mod convert_host;
 pub mod convert_to_hidden_post;
 pub mod escape_sql;
+pub mod fetch_meta;
 pub mod format_milliseconds;
 pub mod id;
 pub mod random;
diff --git a/packages/backend/src/misc/fetch-meta.ts b/packages/backend/src/misc/fetch-meta.ts
index b3a5e30a..2e781082 100644
--- a/packages/backend/src/misc/fetch-meta.ts
+++ b/packages/backend/src/misc/fetch-meta.ts
@@ -1,12 +1,10 @@
-import { db } from "@/db/postgre.js";
-import { Meta } from "@/models/entities/meta.js";
-
-let cache: Meta;
+import type { Meta } from "native-utils/built/index.js";
+import { fetchMeta } from "@/misc/native-utils.js";
 
 export function metaToPugArgs(meta: Meta): object {
 	let motd = ["Loading..."];
-	if (meta.customMOTD.length > 0) {
-		motd = meta.customMOTD;
+	if (meta.customMotd.length > 0) {
+		motd = meta.customMotd;
 	}
 	let splashIconUrl = meta.iconUrl;
 	if (meta.customSplashIcons.length > 0) {
@@ -29,44 +27,7 @@ export function metaToPugArgs(meta: Meta): object {
 	};
 }
 
-export async function fetchMeta(noCache = false): Promise<Meta> {
-	if (!noCache && cache) return cache;
+export { fetchMeta } from "@/misc/native-utils.js";
 
-	return await db.transaction(async (transactionalEntityManager) => {
-		// New IDs are prioritized because multiple records may have been created due to past bugs.
-		const metas = await transactionalEntityManager.find(Meta, {
-			order: {
-				id: "DESC",
-			},
-		});
-
-		const meta = metas[0];
-
-		if (meta) {
-			cache = meta;
-			return meta;
-		} else {
-			// If fetchMeta is called at the same time when meta is empty, this part may be called at the same time, so use fail-safe upsert.
-			const saved = await transactionalEntityManager
-				.upsert(
-					Meta,
-					{
-						id: "x",
-					},
-					["id"],
-				)
-				.then((x) =>
-					transactionalEntityManager.findOneByOrFail(Meta, x.identifiers[0]),
-				);
-
-			cache = saved;
-			return saved;
-		}
-	});
-}
-
-setInterval(() => {
-	fetchMeta(true).then((meta) => {
-		cache = meta;
-	});
-}, 1000 * 10);
+// refresh cache
+setInterval(() => fetchMeta(true), 1000 * 10);
diff --git a/packages/backend/src/misc/native-utils.ts b/packages/backend/src/misc/native-utils.ts
index 1f73e6b9..6d960a52 100644
--- a/packages/backend/src/misc/native-utils.ts
+++ b/packages/backend/src/misc/native-utils.ts
@@ -1,15 +1,21 @@
 import {
 	readServerConfig,
 	readEnvironmentConfig,
+	initDatabase,
+	fetchMeta as fetchMetaImpl,
 	getFullApAccount as getFullApAccountImpl,
 	isSelfHost as isSelfHostImpl,
 } from "native-utils/built/index.js";
 
 export const serverConfig = readServerConfig();
 export const envConfig = readEnvironmentConfig();
+const dbPromise = initDatabase();
 
 type Option<T> = T | null | undefined;
 
+export const fetchMeta = (invalidateCache = false) =>
+	dbPromise.then((db) => fetchMetaImpl(db, invalidateCache));
+
 export function getFullApAccount(
 	username: string,
 	host: Option<string>,
diff --git a/packages/backend/src/remote/activitypub/renderer/like.ts b/packages/backend/src/remote/activitypub/renderer/like.ts
index 060b34ad..6f810cd2 100644
--- a/packages/backend/src/remote/activitypub/renderer/like.ts
+++ b/packages/backend/src/remote/activitypub/renderer/like.ts
@@ -4,7 +4,6 @@ import type { NoteReaction } from "@/models/entities/note-reaction.js";
 import type { Note } from "@/models/entities/note.js";
 import { Emojis } from "@/models/index.js";
 import renderEmoji from "./emoji.js";
-import { fetchMeta } from "@/misc/fetch-meta.js";
 
 export const renderLike = async (noteReaction: NoteReaction, note: Note) => {
 	const reaction = noteReaction.reaction;
diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts
index d168c345..845c8f48 100644
--- a/packages/backend/src/server/api/endpoints/admin/meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/meta.ts
@@ -471,7 +471,7 @@ export default define(meta, paramDef, async (ps) => {
 		uri: config.url,
 		description: instance.description,
 		langs: instance.langs,
-		tosUrl: instance.ToSUrl,
+		tosUrl: instance.toSUrl,
 		moreUrls: instance.moreUrls,
 		repositoryUrl: instance.repositoryUrl,
 		feedbackUrl: instance.feedbackUrl,
@@ -509,7 +509,7 @@ export default define(meta, paramDef, async (ps) => {
 		defaultReaction: instance.defaultReaction,
 		recommendedInstances: instance.recommendedInstances,
 		pinnedUsers: instance.pinnedUsers,
-		customMOTD: instance.customMOTD,
+		customMOTD: instance.customMotd,
 		customSplashIcons: instance.customSplashIcons,
 		hiddenTags: instance.hiddenTags,
 		blockedHosts: instance.blockedHosts,
@@ -543,7 +543,7 @@ export default define(meta, paramDef, async (ps) => {
 		objectStoragePort: instance.objectStoragePort,
 		objectStorageAccessKey: instance.objectStorageAccessKey,
 		objectStorageSecretKey: instance.objectStorageSecretKey,
-		objectStorageUseSSL: instance.objectStorageUseSSL,
+		objectStorageUseSSL: instance.objectStorageUseSsl,
 		objectStorageUseProxy: instance.objectStorageUseProxy,
 		objectStorageSetPublicRead: instance.objectStorageSetPublicRead,
 		objectStorageS3ForcePathStyle: instance.objectStorageS3ForcePathStyle,
diff --git a/packages/backend/src/server/api/endpoints/custom-motd.ts b/packages/backend/src/server/api/endpoints/custom-motd.ts
index d61b31fe..2939355b 100644
--- a/packages/backend/src/server/api/endpoints/custom-motd.ts
+++ b/packages/backend/src/server/api/endpoints/custom-motd.ts
@@ -28,6 +28,6 @@ export const paramDef = {
 
 export default define(meta, paramDef, async () => {
 	const meta = await fetchMeta();
-	const motd = await Promise.all(meta.customMOTD.map((x) => x));
+	const motd = await Promise.all(meta.customMotd.map((x) => x));
 	return motd;
 });
diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts
index 977d57a3..2c92f36e 100644
--- a/packages/backend/src/server/api/endpoints/meta.ts
+++ b/packages/backend/src/server/api/endpoints/meta.ts
@@ -425,7 +425,7 @@ export default define(meta, paramDef, async (ps, me) => {
 		uri: config.url,
 		description: instance.description,
 		langs: instance.langs,
-		tosUrl: instance.ToSUrl,
+		tosUrl: instance.toSUrl,
 		moreUrls: instance.moreUrls,
 		repositoryUrl: instance.repositoryUrl,
 		feedbackUrl: instance.feedbackUrl,
diff --git a/packages/backend/src/server/nodeinfo.ts b/packages/backend/src/server/nodeinfo.ts
index b7be1975..8c78accb 100644
--- a/packages/backend/src/server/nodeinfo.ts
+++ b/packages/backend/src/server/nodeinfo.ts
@@ -74,7 +74,7 @@ const nodeinfo2 = async () => {
 				email: meta.maintainerEmail,
 			},
 			langs: meta.langs,
-			tosUrl: meta.ToSUrl,
+			tosUrl: meta.toSUrl,
 			repositoryUrl: meta.repositoryUrl,
 			feedbackUrl: meta.feedbackUrl,
 			disableRegistration: meta.disableRegistration,