diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6c7a7c2e5..5e168ea54 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,11 @@ and this project adheres to
 
 🔧(helm) add option to disable default tls setting by @dominikkaminski #519
 
+## Changed
+
+- 🏗️(yjs-server) organize yjs server #528
+- ♻️(frontend) better separation collaboration process #528
+
 
 ## [1.10.0] - 2024-12-17
 
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/common.ts b/src/frontend/apps/e2e/__tests__/app-impress/common.ts
index 900d32651..8408e38d7 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/common.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/common.ts
@@ -41,7 +41,7 @@ export const createDoc = async (
       .click();
 
     await page.getByRole('heading', { name: 'Untitled document' }).click();
-    await page.keyboard.type(randomDocs[i]);
+    await page.keyboard.type(randomDocs[i], { delay: 100 });
     await page.getByText('Created at ').click();
   }
 
diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocEditor.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocEditor.tsx
index 935e193ac..bd165df1e 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocEditor.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/DocEditor.tsx
@@ -10,7 +10,7 @@ import { DocHeader } from '@/features/docs/doc-header';
 import {
   Doc,
   base64ToBlocknoteXmlFragment,
-  useDocStore,
+  useProviderStore,
 } from '@/features/docs/doc-management';
 import { Versions, useDocVersion } from '@/features/docs/doc-versioning/';
 import { useResponsiveStore } from '@/stores';
@@ -33,8 +33,7 @@ export const DocEditor = ({ doc }: DocEditorProps) => {
 
   const { colorsTokens } = useCunninghamTheme();
 
-  const { providers } = useDocStore();
-  const provider = providers?.[doc.id];
+  const { provider } = useProviderStore();
 
   if (!provider) {
     return null;
diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/hooks/index.ts b/src/frontend/apps/impress/src/features/docs/doc-management/hooks/index.ts
index 7697d915a..b352e1890 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-management/hooks/index.ts
+++ b/src/frontend/apps/impress/src/features/docs/doc-management/hooks/index.ts
@@ -1 +1,2 @@
+export * from './useCollaboration';
 export * from './useTrans';
diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/hooks/useCollaboration.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/hooks/useCollaboration.tsx
new file mode 100644
index 000000000..848ccd005
--- /dev/null
+++ b/src/frontend/apps/impress/src/features/docs/doc-management/hooks/useCollaboration.tsx
@@ -0,0 +1,35 @@
+import { useEffect } from 'react';
+
+import { useCollaborationUrl } from '@/core/config';
+import { useBroadcastStore } from '@/stores';
+
+import { useProviderStore } from '../stores/useProviderStore';
+import { Base64 } from '../types';
+
+export const useCollaboration = (room?: string, initialContent?: Base64) => {
+  const collaborationUrl = useCollaborationUrl(room);
+  const { setBroadcastProvider } = useBroadcastStore();
+  const { provider, createProvider, destroyProvider } = useProviderStore();
+
+  useEffect(() => {
+    if (!room || !collaborationUrl || provider) {
+      return;
+    }
+
+    const newProvider = createProvider(collaborationUrl, room, initialContent);
+    setBroadcastProvider(newProvider);
+  }, [
+    provider,
+    collaborationUrl,
+    room,
+    initialContent,
+    createProvider,
+    setBroadcastProvider,
+  ]);
+
+  useEffect(() => {
+    return () => {
+      destroyProvider();
+    };
+  }, [destroyProvider]);
+};
diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/stores/index.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/stores/index.tsx
index e742cba56..9926e4e32 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-management/stores/index.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-management/stores/index.tsx
@@ -1 +1,2 @@
 export * from './useDocStore';
+export * from './useProviderStore';
diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/stores/useDocStore.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/stores/useDocStore.tsx
index fbc4de4cc..449995a75 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-management/stores/useDocStore.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-management/stores/useDocStore.tsx
@@ -1,53 +1,14 @@
-import { HocuspocusProvider } from '@hocuspocus/provider';
-import * as Y from 'yjs';
 import { create } from 'zustand';
 
-import { Base64, Doc } from '@/features/docs/doc-management';
+import { Doc } from '@/features/docs/doc-management';
 
 export interface UseDocStore {
   currentDoc?: Doc;
-  providers: {
-    [storeId: string]: HocuspocusProvider;
-  };
-  createProvider: (
-    providerUrl: string,
-    storeId: string,
-    initialDoc: Base64,
-  ) => HocuspocusProvider;
-  setProviders: (storeId: string, providers: HocuspocusProvider) => void;
   setCurrentDoc: (doc: Doc | undefined) => void;
 }
 
-export const useDocStore = create<UseDocStore>((set, get) => ({
+export const useDocStore = create<UseDocStore>((set) => ({
   currentDoc: undefined,
-  providers: {},
-  createProvider: (providerUrl, storeId, initialDoc) => {
-    const doc = new Y.Doc({
-      guid: storeId,
-    });
-
-    if (initialDoc) {
-      Y.applyUpdate(doc, Buffer.from(initialDoc, 'base64'));
-    }
-
-    const provider = new HocuspocusProvider({
-      url: providerUrl,
-      name: storeId,
-      document: doc,
-    });
-
-    get().setProviders(storeId, provider);
-
-    return provider;
-  },
-  setProviders: (storeId, provider) => {
-    set(({ providers }) => ({
-      providers: {
-        ...providers,
-        [storeId]: provider,
-      },
-    }));
-  },
   setCurrentDoc: (doc) => {
     set({ currentDoc: doc });
   },
diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/stores/useProviderStore.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/stores/useProviderStore.tsx
new file mode 100644
index 000000000..a638045a1
--- /dev/null
+++ b/src/frontend/apps/impress/src/features/docs/doc-management/stores/useProviderStore.tsx
@@ -0,0 +1,52 @@
+import { HocuspocusProvider } from '@hocuspocus/provider';
+import * as Y from 'yjs';
+import { create } from 'zustand';
+
+import { Base64 } from '@/features/docs/doc-management';
+
+export interface UseCollaborationStore {
+  createProvider: (
+    providerUrl: string,
+    storeId: string,
+    initialDoc?: Base64,
+  ) => HocuspocusProvider;
+  destroyProvider: () => void;
+  provider: HocuspocusProvider | undefined;
+}
+
+const defaultValues = {
+  provider: undefined,
+};
+
+export const useProviderStore = create<UseCollaborationStore>((set, get) => ({
+  ...defaultValues,
+  createProvider: (wsUrl, storeId, initialDoc) => {
+    const doc = new Y.Doc({
+      guid: storeId,
+    });
+
+    if (initialDoc) {
+      Y.applyUpdate(doc, Buffer.from(initialDoc, 'base64'));
+    }
+
+    const provider = new HocuspocusProvider({
+      url: wsUrl,
+      name: storeId,
+      document: doc,
+    });
+
+    set({
+      provider,
+    });
+
+    return provider;
+  },
+  destroyProvider: () => {
+    const provider = get().provider;
+    if (provider) {
+      provider.destroy();
+    }
+
+    set(defaultValues);
+  },
+}));
diff --git a/src/frontend/apps/impress/src/features/docs/doc-versioning/components/ModalVersion.tsx b/src/frontend/apps/impress/src/features/docs/doc-versioning/components/ModalVersion.tsx
index 8ffba2db3..d1f6588c0 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-versioning/components/ModalVersion.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-versioning/components/ModalVersion.tsx
@@ -13,9 +13,9 @@ import { Box, Text } from '@/components';
 import {
   Doc,
   base64ToYDoc,
-  useDocStore,
+  useProviderStore,
   useUpdateDoc,
-} from '@/features/docs/doc-management';
+} from '@/features/docs/doc-management/';
 
 import { useDocVersion } from '../api';
 import { KEY_LIST_DOC_VERSIONS } from '../api/useDocVersions';
@@ -40,7 +40,7 @@ export const ModalVersion = ({
   const { t } = useTranslation();
   const { toast } = useToastProvider();
   const { push } = useRouter();
-  const { providers } = useDocStore();
+  const { provider } = useProviderStore();
   const { mutate: updateDoc } = useUpdateDoc({
     listInvalideQueries: [KEY_LIST_DOC_VERSIONS],
     onSuccess: () => {
@@ -49,14 +49,14 @@ export const ModalVersion = ({
         void push(`/docs/${docId}`);
       };
 
-      if (!providers?.[docId] || !version?.content) {
+      if (!provider || !version?.content) {
         onDisplaySuccess();
         return;
       }
 
       revertUpdate(
-        providers[docId].document,
-        providers[docId].document,
+        provider.document,
+        provider.document,
         base64ToYDoc(version.content),
       );
 
diff --git a/src/frontend/apps/impress/src/pages/docs/[id]/index.tsx b/src/frontend/apps/impress/src/pages/docs/[id]/index.tsx
index a51c2bca4..a84119ca5 100644
--- a/src/frontend/apps/impress/src/pages/docs/[id]/index.tsx
+++ b/src/frontend/apps/impress/src/pages/docs/[id]/index.tsx
@@ -6,10 +6,15 @@ import { useEffect, useState } from 'react';
 
 import { Box, Text } from '@/components';
 import { TextErrors } from '@/components/TextErrors';
-import { useCollaborationUrl } from '@/core';
 import { useAuthStore } from '@/core/auth';
 import { DocEditor } from '@/features/docs/doc-editor';
-import { KEY_DOC, useDoc, useDocStore } from '@/features/docs/doc-management';
+import {
+  Doc,
+  KEY_DOC,
+  useCollaboration,
+  useDoc,
+  useDocStore,
+} from '@/features/docs/doc-management/';
 import { MainLayout } from '@/layouts';
 import { useBroadcastStore } from '@/stores';
 import { NextPageWithLayout } from '@/types/next';
@@ -41,14 +46,25 @@ interface DocProps {
 
 const DocPage = ({ id }: DocProps) => {
   const { login } = useAuthStore();
-  const { data: docQuery, isError, error } = useDoc({ id });
-  const [doc, setDoc] = useState(docQuery);
-  const { setCurrentDoc, createProvider, providers } = useDocStore();
-  const { setBroadcastProvider, addTask } = useBroadcastStore();
+  const {
+    data: docQuery,
+    isError,
+    isFetching,
+    error,
+  } = useDoc(
+    { id },
+    {
+      staleTime: 0,
+      queryKey: [KEY_DOC, { id }],
+    },
+  );
+
+  const [doc, setDoc] = useState<Doc>();
+  const { setCurrentDoc } = useDocStore();
+  const { addTask } = useBroadcastStore();
   const queryClient = useQueryClient();
   const { replace } = useRouter();
-  const provider = providers?.[id];
-  const collaborationUrl = useCollaborationUrl(doc?.id);
+  useCollaboration(doc?.id, doc?.content);
 
   useEffect(() => {
     if (doc?.title) {
@@ -59,26 +75,13 @@ const DocPage = ({ id }: DocProps) => {
   }, [doc?.title]);
 
   useEffect(() => {
-    if (!docQuery) {
+    if (!docQuery || isFetching) {
       return;
     }
 
     setDoc(docQuery);
     setCurrentDoc(docQuery);
-  }, [docQuery, setCurrentDoc]);
-
-  useEffect(() => {
-    if (!doc?.id || !collaborationUrl) {
-      return;
-    }
-
-    let newProvider = provider;
-    if (!provider || provider.document.guid !== doc.id) {
-      newProvider = createProvider(collaborationUrl, doc.id, doc.content);
-    }
-
-    setBroadcastProvider(newProvider);
-  }, [createProvider, doc, provider, setBroadcastProvider, collaborationUrl]);
+  }, [docQuery, setCurrentDoc, isFetching]);
 
   /**
    * We add a broadcast task to reset the query cache
diff --git a/src/frontend/apps/impress/src/stores/useBroadcastStore.tsx b/src/frontend/apps/impress/src/stores/useBroadcastStore.tsx
index a6230ea6c..7e8812f76 100644
--- a/src/frontend/apps/impress/src/stores/useBroadcastStore.tsx
+++ b/src/frontend/apps/impress/src/stores/useBroadcastStore.tsx
@@ -8,7 +8,20 @@ interface BroadcastState {
   getBroadcastProvider: () => HocuspocusProvider | undefined;
   provider?: HocuspocusProvider;
   setBroadcastProvider: (provider: HocuspocusProvider) => void;
-  tasks: { [taskLabel: string]: Y.Array<string> };
+  setTask: (
+    taskLabel: string,
+    task: Y.Array<string>,
+    action: () => void,
+  ) => void;
+  tasks: {
+    [taskLabel: string]: {
+      task: Y.Array<string>;
+      observer: (
+        event: Y.YArrayEvent<string>,
+        transaction: Y.Transaction,
+      ) => void;
+    };
+  };
 }
 
 export const useBroadcastStore = create<BroadcastState>((set, get) => ({
@@ -25,26 +38,47 @@ export const useBroadcastStore = create<BroadcastState>((set, get) => ({
     return provider;
   },
   addTask: (taskLabel, action) => {
-    const taskExistAlready = get().tasks[taskLabel];
     const provider = get().getBroadcastProvider();
-    if (taskExistAlready || !provider) {
+    if (!provider) {
+      return;
+    }
+
+    const existingTask = get().tasks[taskLabel];
+    if (existingTask) {
+      existingTask.task.unobserve(existingTask.observer);
+      get().setTask(taskLabel, existingTask.task, action);
       return;
     }
 
     const task = provider.document.getArray<string>(taskLabel);
-    task.observe(() => {
-      action();
-    });
+    get().setTask(taskLabel, task, action);
+  },
+  setTask: (taskLabel: string, task: Y.Array<string>, action: () => void) => {
+    let isInitializing = true;
+    const observer = () => {
+      if (!isInitializing) {
+        action();
+      }
+    };
+
+    task.observe(observer);
+
+    setTimeout(() => {
+      isInitializing = false;
+    }, 1000);
 
     set((state) => ({
       tasks: {
         ...state.tasks,
-        [taskLabel]: task,
+        [taskLabel]: {
+          task,
+          observer,
+        },
       },
     }));
   },
   broadcast: (taskLabel) => {
-    const task = get().tasks[taskLabel];
+    const { task } = get().tasks[taskLabel];
     if (!task) {
       console.warn(`Task ${taskLabel} is not defined`);
       return;
diff --git a/src/frontend/servers/y-provider/__tests__/collaborationResetConnections.test.ts b/src/frontend/servers/y-provider/__tests__/collaborationResetConnections.test.ts
new file mode 100644
index 000000000..023c09782
--- /dev/null
+++ b/src/frontend/servers/y-provider/__tests__/collaborationResetConnections.test.ts
@@ -0,0 +1,66 @@
+import request from 'supertest';
+
+const port = 5555;
+const origin = 'http://localhost:3000';
+
+jest.mock('../src/env', () => {
+  return {
+    PORT: port,
+    COLLABORATION_SERVER_ORIGIN: origin,
+    COLLABORATION_SERVER_SECRET: 'test-secret-api-key',
+  };
+});
+
+console.error = jest.fn();
+
+import { hocusPocusServer } from '@/servers/hocusPocusServer';
+
+import { initServer } from '../src/servers/appServer';
+
+const { app, server } = initServer();
+
+describe('Server Tests', () => {
+  afterAll(() => {
+    server.close();
+  });
+
+  test('POST /collaboration/api/reset-connections?room=[ROOM_ID] with incorrect API key should return 403', async () => {
+    const response = await request(app as any)
+      .post('/collaboration/api/reset-connections/?room=test-room')
+      .set('Origin', origin)
+      .set('Authorization', 'wrong-api-key');
+
+    expect(response.status).toBe(403);
+    expect(response.body.error).toBe('Forbidden: Invalid API Key');
+  });
+
+  test('POST /collaboration/api/reset-connections?room=[ROOM_ID] failed if room not indicated', async () => {
+    const response = await request(app as any)
+      .post('/collaboration/api/reset-connections/')
+      .set('Origin', origin)
+      .set('Authorization', 'test-secret-api-key')
+      .send({ document_id: 'test-document' });
+
+    expect(response.status).toBe(400);
+    expect(response.body.error).toBe('Room name not provided');
+  });
+
+  test('POST /collaboration/api/reset-connections?room=[ROOM_ID] with correct API key should reset connections', async () => {
+    // eslint-disable-next-line jest/unbound-method
+    const { closeConnections } = hocusPocusServer;
+    const mockHandleConnection = jest.fn();
+    (hocusPocusServer.closeConnections as jest.Mock) = mockHandleConnection;
+
+    const response = await request(app as any)
+      .post('/collaboration/api/reset-connections?room=test-room')
+      .set('Origin', origin)
+      .set('Authorization', 'test-secret-api-key');
+
+    expect(response.status).toBe(200);
+    expect(response.body.message).toBe('Connections reset');
+
+    expect(mockHandleConnection).toHaveBeenCalled();
+    mockHandleConnection.mockClear();
+    hocusPocusServer.closeConnections = closeConnections;
+  });
+});
diff --git a/src/frontend/servers/y-provider/__tests__/convertMarkdown.test.ts b/src/frontend/servers/y-provider/__tests__/convertMarkdown.test.ts
new file mode 100644
index 000000000..ed4765b2e
--- /dev/null
+++ b/src/frontend/servers/y-provider/__tests__/convertMarkdown.test.ts
@@ -0,0 +1,66 @@
+import request from 'supertest';
+
+const port = 5556;
+const origin = 'http://localhost:3000';
+
+jest.mock('../src/env', () => {
+  return {
+    PORT: port,
+    COLLABORATION_SERVER_ORIGIN: origin,
+    Y_PROVIDER_API_KEY: 'yprovider-api-key',
+  };
+});
+
+import { initServer } from '../src/servers/appServer';
+
+console.error = jest.fn();
+const { app, server } = initServer();
+
+describe('Server Tests', () => {
+  afterAll(() => {
+    server.close();
+  });
+
+  test('POST /api/convert-markdown with incorrect API key should return 403', async () => {
+    const response = await request(app as any)
+      .post('/api/convert-markdown')
+      .set('Origin', origin)
+      .set('Authorization', 'wrong-api-key');
+
+    expect(response.status).toBe(403);
+    expect(response.body.error).toBe('Forbidden: Invalid API Key');
+  });
+
+  test('POST /api/convert-markdown with a Bearer token', async () => {
+    const response = await request(app as any)
+      .post('/api/convert-markdown')
+      .set('Origin', origin)
+      .set('Authorization', 'Bearer test-secret-api-key');
+
+    // Warning: Changing the authorization header to Bearer token format will break backend compatibility with this microservice.
+    expect(response.status).toBe(403);
+  });
+
+  test('POST /api/convert-markdown with missing body param content', async () => {
+    const response = await request(app as any)
+      .post('/api/convert-markdown')
+      .set('Origin', origin)
+      .set('Authorization', 'yprovider-api-key');
+
+    expect(response.status).toBe(400);
+    expect(response.body.error).toBe('Invalid request: missing content');
+  });
+
+  test('POST /api/convert-markdown with body param content being an empty string', async () => {
+    const response = await request(app as any)
+      .post('/api/convert-markdown')
+      .set('Origin', origin)
+      .set('Authorization', 'yprovider-api-key')
+      .send({
+        content: '',
+      });
+
+    expect(response.status).toBe(400);
+    expect(response.body.error).toBe('Invalid request: missing content');
+  });
+});
diff --git a/src/frontend/servers/y-provider/__tests__/hocusPocusWS.test.ts b/src/frontend/servers/y-provider/__tests__/hocusPocusWS.test.ts
new file mode 100644
index 000000000..6499c31bd
--- /dev/null
+++ b/src/frontend/servers/y-provider/__tests__/hocusPocusWS.test.ts
@@ -0,0 +1,169 @@
+import {
+  HocuspocusProvider,
+  HocuspocusProviderWebsocket,
+} from '@hocuspocus/provider';
+import WebSocket from 'ws';
+
+const port = 5559;
+const portWS = 6666;
+const origin = 'http://localhost:3000';
+
+jest.mock('../src/env', () => {
+  return {
+    PORT: port,
+    COLLABORATION_SERVER_ORIGIN: origin,
+    COLLABORATION_SERVER_SECRET: 'test-secret-api-key',
+  };
+});
+
+console.error = jest.fn();
+
+import { hocusPocusServer } from '@/servers/hocusPocusServer';
+
+import { promiseDone } from '../src/helpers';
+import { initServer } from '../src/servers/appServer';
+
+const { server } = initServer();
+
+describe('Server Tests', () => {
+  beforeAll(async () => {
+    await hocusPocusServer.configure({ port: portWS }).listen();
+  });
+
+  afterAll(() => {
+    server.close();
+    void hocusPocusServer.destroy();
+  });
+
+  test('WebSocket connection with correct API key can connect', () => {
+    const { promise, done } = promiseDone();
+
+    // eslint-disable-next-line jest/unbound-method
+    const { handleConnection } = hocusPocusServer;
+    const mockHandleConnection = jest.fn();
+    (hocusPocusServer.handleConnection as jest.Mock) = mockHandleConnection;
+
+    const clientWS = new WebSocket(
+      `ws://localhost:${port}/collaboration/ws/?room=test-room`,
+      {
+        headers: {
+          authorization: 'test-secret-api-key',
+          Origin: origin,
+        },
+      },
+    );
+
+    clientWS.on('open', () => {
+      expect(mockHandleConnection).toHaveBeenCalled();
+      clientWS.close();
+      mockHandleConnection.mockClear();
+      hocusPocusServer.handleConnection = handleConnection;
+      done();
+    });
+
+    return promise;
+  });
+
+  test('WebSocket connection with bad origin should be closed', () => {
+    const { promise, done } = promiseDone();
+
+    const ws = new WebSocket(
+      `ws://localhost:${port}/collaboration/ws/?room=test-room`,
+      {
+        headers: {
+          Origin: 'http://bad-origin.com',
+        },
+      },
+    );
+
+    ws.onclose = () => {
+      expect(ws.readyState).toBe(ws.CLOSED);
+      done();
+    };
+
+    return promise;
+  });
+
+  test('WebSocket connection with incorrect API key should be closed', () => {
+    const { promise, done } = promiseDone();
+    const ws = new WebSocket(
+      `ws://localhost:${port}/collaboration/ws/?room=test-room`,
+      {
+        headers: {
+          Authorization: 'wrong-api-key',
+          Origin: origin,
+        },
+      },
+    );
+
+    ws.onclose = () => {
+      expect(ws.readyState).toBe(ws.CLOSED);
+      done();
+    };
+
+    return promise;
+  });
+
+  test('WebSocket connection not allowed if room not matching provider name', () => {
+    const { promise, done } = promiseDone();
+
+    const wsHocus = new HocuspocusProviderWebsocket({
+      url: `ws://localhost:${portWS}/?room=my-test`,
+      WebSocketPolyfill: WebSocket,
+      maxAttempts: 1,
+      quiet: true,
+    });
+
+    const provider = new HocuspocusProvider({
+      websocketProvider: wsHocus,
+      name: 'hocuspocus-test',
+      broadcast: false,
+      quiet: true,
+      preserveConnection: false,
+      onClose: (data) => {
+        wsHocus.stopConnectionAttempt();
+        expect(data.event.reason).toBe('Forbidden');
+        wsHocus.webSocket?.close();
+        wsHocus.disconnect();
+        provider.destroy();
+        wsHocus.destroy();
+        done();
+      },
+    });
+
+    return promise;
+  });
+
+  test('WebSocket connection read-only', () => {
+    const { promise, done } = promiseDone();
+
+    const wsHocus = new HocuspocusProviderWebsocket({
+      url: `ws://localhost:${portWS}/?room=hocuspocus-test`,
+      WebSocketPolyfill: WebSocket,
+    });
+
+    const provider = new HocuspocusProvider({
+      websocketProvider: wsHocus,
+      name: 'hocuspocus-test',
+      broadcast: false,
+      quiet: true,
+      onConnect: () => {
+        void hocusPocusServer
+          .openDirectConnection('hocuspocus-test')
+          .then((connection) => {
+            connection.document?.getConnections().forEach((connection) => {
+              expect(connection.readOnly).toBe(true);
+            });
+
+            void connection.disconnect();
+          });
+
+        provider.destroy();
+        wsHocus.destroy();
+        done();
+      },
+    });
+
+    return promise;
+  });
+});
diff --git a/src/frontend/servers/y-provider/__tests__/server.test.ts b/src/frontend/servers/y-provider/__tests__/server.test.ts
index c1ff43935..2f048bff2 100644
--- a/src/frontend/servers/y-provider/__tests__/server.test.ts
+++ b/src/frontend/servers/y-provider/__tests__/server.test.ts
@@ -1,38 +1,24 @@
-import {
-  HocuspocusProvider,
-  HocuspocusProviderWebsocket,
-} from '@hocuspocus/provider';
 import request from 'supertest';
-import WebSocket from 'ws';
 
-const port = 5555;
-const portWS = 6666;
+const port = 5557;
 const origin = 'http://localhost:3000';
 
 jest.mock('../src/env', () => {
   return {
     PORT: port,
     COLLABORATION_SERVER_ORIGIN: origin,
-    COLLABORATION_SERVER_SECRET: 'test-secret-api-key',
-    Y_PROVIDER_API_KEY: 'yprovider-api-key',
   };
 });
 
 console.error = jest.fn();
 
-import { promiseDone } from '../src/helpers';
-import { hocuspocusServer, initServer } from '../src/server'; // Adjust the path to your server file
+import { initServer } from '../src/servers/appServer';
 
 const { app, server } = initServer();
 
 describe('Server Tests', () => {
-  beforeAll(async () => {
-    await hocuspocusServer.configure({ port: portWS }).listen();
-  });
-
   afterAll(() => {
     server.close();
-    void hocuspocusServer.destroy();
   });
 
   test('Ping Pong', async () => {
@@ -42,99 +28,6 @@ describe('Server Tests', () => {
     expect(response.body.message).toBe('pong');
   });
 
-  test('POST /collaboration/api/reset-connections?room=[ROOM_ID] invalid origin', async () => {
-    const response = await request(app as any)
-      .post('/collaboration/api/reset-connections/?room=test-room')
-      .set('Origin', 'http://invalid-origin.com')
-      .send({ document_id: 'test-document' });
-
-    expect(response.status).toBe(403);
-    expect(response.body.error).toBe('CORS policy violation: Invalid Origin');
-  });
-
-  test('POST /collaboration/api/reset-connections?room=[ROOM_ID] with incorrect API key should return 403', async () => {
-    const response = await request(app as any)
-      .post('/collaboration/api/reset-connections/?room=test-room')
-      .set('Origin', origin)
-      .set('Authorization', 'wrong-api-key');
-
-    expect(response.status).toBe(403);
-    expect(response.body.error).toBe('Forbidden: Invalid API Key');
-  });
-
-  test('POST /collaboration/api/reset-connections?room=[ROOM_ID] failed if room not indicated', async () => {
-    const response = await request(app as any)
-      .post('/collaboration/api/reset-connections/')
-      .set('Origin', origin)
-      .set('Authorization', 'test-secret-api-key')
-      .send({ document_id: 'test-document' });
-
-    expect(response.status).toBe(400);
-    expect(response.body.error).toBe('Room name not provided');
-  });
-
-  test('POST /collaboration/api/reset-connections?room=[ROOM_ID] with correct API key should reset connections', async () => {
-    // eslint-disable-next-line jest/unbound-method
-    const { closeConnections } = hocuspocusServer;
-    const mockHandleConnection = jest.fn();
-    (hocuspocusServer.closeConnections as jest.Mock) = mockHandleConnection;
-
-    const response = await request(app as any)
-      .post('/collaboration/api/reset-connections?room=test-room')
-      .set('Origin', origin)
-      .set('Authorization', 'test-secret-api-key');
-
-    expect(response.status).toBe(200);
-    expect(response.body.message).toBe('Connections reset');
-
-    expect(mockHandleConnection).toHaveBeenCalled();
-    mockHandleConnection.mockClear();
-    hocuspocusServer.closeConnections = closeConnections;
-  });
-
-  test('POST /api/convert-markdown with incorrect API key should return 403', async () => {
-    const response = await request(app as any)
-      .post('/api/convert-markdown')
-      .set('Origin', origin)
-      .set('Authorization', 'wrong-api-key');
-
-    expect(response.status).toBe(403);
-    expect(response.body.error).toBe('Forbidden: Invalid API Key');
-  });
-
-  test('POST /api/convert-markdown with a Bearer token', async () => {
-    const response = await request(app as any)
-      .post('/api/convert-markdown')
-      .set('Origin', origin)
-      .set('Authorization', 'Bearer test-secret-api-key');
-
-    // Warning: Changing the authorization header to Bearer token format will break backend compatibility with this microservice.
-    expect(response.status).toBe(403);
-  });
-
-  test('POST /api/convert-markdown with missing body param content', async () => {
-    const response = await request(app as any)
-      .post('/api/convert-markdown')
-      .set('Origin', origin)
-      .set('Authorization', 'yprovider-api-key');
-
-    expect(response.status).toBe(400);
-    expect(response.body.error).toBe('Invalid request: missing content');
-  });
-
-  test('POST /api/convert-markdown with body param content being an empty string', async () => {
-    const response = await request(app as any)
-      .post('/api/convert-markdown')
-      .set('Origin', origin)
-      .set('Authorization', 'yprovider-api-key')
-      .send({
-        content: '',
-      });
-
-    expect(response.status).toBe(400);
-    expect(response.body.error).toBe('Invalid request: missing content');
-  });
-
   ['/collaboration/api/anything/', '/', '/anything'].forEach((path) => {
     test(`"${path}" endpoint should be forbidden`, async () => {
       const response = await request(app as any).post(path);
@@ -143,136 +36,4 @@ describe('Server Tests', () => {
       expect(response.body.error).toBe('Forbidden');
     });
   });
-
-  test('WebSocket connection with correct API key can connect', () => {
-    const { promise, done } = promiseDone();
-
-    // eslint-disable-next-line jest/unbound-method
-    const { handleConnection } = hocuspocusServer;
-    const mockHandleConnection = jest.fn();
-    (hocuspocusServer.handleConnection as jest.Mock) = mockHandleConnection;
-
-    const clientWS = new WebSocket(
-      `ws://localhost:${port}/collaboration/ws/?room=test-room`,
-      {
-        headers: {
-          authorization: 'test-secret-api-key',
-          Origin: origin,
-        },
-      },
-    );
-
-    clientWS.on('open', () => {
-      expect(mockHandleConnection).toHaveBeenCalled();
-      clientWS.close();
-      mockHandleConnection.mockClear();
-      hocuspocusServer.handleConnection = handleConnection;
-      done();
-    });
-
-    return promise;
-  });
-
-  test('WebSocket connection with bad origin should be closed', () => {
-    const { promise, done } = promiseDone();
-
-    const ws = new WebSocket(
-      `ws://localhost:${port}/collaboration/ws/?room=test-room`,
-      {
-        headers: {
-          Origin: 'http://bad-origin.com',
-        },
-      },
-    );
-
-    ws.onclose = () => {
-      expect(ws.readyState).toBe(ws.CLOSED);
-      done();
-    };
-
-    return promise;
-  });
-
-  test('WebSocket connection with incorrect API key should be closed', () => {
-    const { promise, done } = promiseDone();
-    const ws = new WebSocket(
-      `ws://localhost:${port}/collaboration/ws/?room=test-room`,
-      {
-        headers: {
-          Authorization: 'wrong-api-key',
-          Origin: origin,
-        },
-      },
-    );
-
-    ws.onclose = () => {
-      expect(ws.readyState).toBe(ws.CLOSED);
-      done();
-    };
-
-    return promise;
-  });
-
-  test('WebSocket connection not allowed if room not matching provider name', () => {
-    const { promise, done } = promiseDone();
-
-    const wsHocus = new HocuspocusProviderWebsocket({
-      url: `ws://localhost:${portWS}/?room=my-test`,
-      WebSocketPolyfill: WebSocket,
-      maxAttempts: 1,
-      quiet: true,
-    });
-
-    const provider = new HocuspocusProvider({
-      websocketProvider: wsHocus,
-      name: 'hocuspocus-test',
-      broadcast: false,
-      quiet: true,
-      preserveConnection: false,
-      onClose: (data) => {
-        wsHocus.stopConnectionAttempt();
-        expect(data.event.reason).toBe('Forbidden');
-        wsHocus.webSocket?.close();
-        wsHocus.disconnect();
-        provider.destroy();
-        wsHocus.destroy();
-        done();
-      },
-    });
-
-    return promise;
-  });
-
-  test('WebSocket connection read-only', () => {
-    const { promise, done } = promiseDone();
-
-    const wsHocus = new HocuspocusProviderWebsocket({
-      url: `ws://localhost:${portWS}/?room=hocuspocus-test`,
-      WebSocketPolyfill: WebSocket,
-    });
-
-    const provider = new HocuspocusProvider({
-      websocketProvider: wsHocus,
-      name: 'hocuspocus-test',
-      broadcast: false,
-      quiet: true,
-      onConnect: () => {
-        void hocuspocusServer
-          .openDirectConnection('hocuspocus-test')
-          .then((connection) => {
-            connection.document?.getConnections().forEach((connection) => {
-              expect(connection.readOnly).toBe(true);
-            });
-
-            void connection.disconnect();
-          });
-
-        provider.destroy();
-        wsHocus.destroy();
-        done();
-      },
-    });
-
-    return promise;
-  });
 });
diff --git a/src/frontend/servers/y-provider/nodemon.json b/src/frontend/servers/y-provider/nodemon.json
index e9329fd9b..0c61bfb3f 100644
--- a/src/frontend/servers/y-provider/nodemon.json
+++ b/src/frontend/servers/y-provider/nodemon.json
@@ -1,5 +1,5 @@
 {
   "watch": ["src"],
   "ext": "ts",
-  "exec": "yarn build"
+  "exec": "yarn build && yarn start"
 }
diff --git a/src/frontend/servers/y-provider/package.json b/src/frontend/servers/y-provider/package.json
index dff606b7c..c32d2e78a 100644
--- a/src/frontend/servers/y-provider/package.json
+++ b/src/frontend/servers/y-provider/package.json
@@ -7,7 +7,7 @@
   "type": "module",
   "scripts": {
     "build": "tsc -p tsconfig.build.json && tsc-alias",
-    "dev": "nodemon --config nodemon.json",
+    "dev": "cross-env COLLABORATION_LOGGING=true && nodemon --config nodemon.json",
     "start": "node ./dist/start-server.js",
     "lint": "eslint . --ext .ts",
     "test": "jest"
@@ -20,6 +20,7 @@
     "@hocuspocus/server": "2.15.0",
     "@sentry/node": "8.45.1",
     "@sentry/profiling-node": "8.45.1",
+    "cors": "2.8.5",
     "express": "4.21.2",
     "express-ws": "5.0.2",
     "y-protocols": "1.0.6",
@@ -27,6 +28,7 @@
   },
   "devDependencies": {
     "@hocuspocus/provider": "2.15.0",
+    "@types/cors": "2.8.17",
     "@types/express": "5.0.0",
     "@types/express-ws": "3.0.5",
     "@types/jest": "29.5.14",
@@ -34,6 +36,7 @@
     "@types/supertest": "6.0.2",
     "@types/ws": "8.5.13",
     "eslint-config-impress": "*",
+    "cross-env": "*",
     "jest": "29.7.0",
     "nodemon": "3.1.9",
     "supertest": "7.0.0",
diff --git a/src/frontend/servers/y-provider/src/handlers/collaborationResetConnectionsHandler.ts b/src/frontend/servers/y-provider/src/handlers/collaborationResetConnectionsHandler.ts
new file mode 100644
index 000000000..4af440967
--- /dev/null
+++ b/src/frontend/servers/y-provider/src/handlers/collaborationResetConnectionsHandler.ts
@@ -0,0 +1,55 @@
+import { Request, Response } from 'express';
+
+import { hocusPocusServer } from '@/servers/hocusPocusServer';
+import { logger } from '@/utils';
+
+type ResetConnectionsRequestQuery = {
+  room?: string;
+};
+
+export const collaborationResetConnectionsHandler = (
+  req: Request<object, object, object, ResetConnectionsRequestQuery>,
+  res: Response,
+) => {
+  const room = req.query.room;
+  const userId = req.headers['x-user-id'];
+
+  logger(
+    'Resetting connections in room:',
+    room,
+    'for user:',
+    userId,
+    'room:',
+    room,
+  );
+
+  if (!room) {
+    res.status(400).json({ error: 'Room name not provided' });
+    return;
+  }
+
+  /**
+   * If no user ID is provided, close all connections in the room
+   */
+  if (!userId) {
+    hocusPocusServer.closeConnections(room);
+  } else {
+    /**
+     * Close connections for the user in the room
+     */
+    hocusPocusServer.documents.forEach((doc) => {
+      if (doc.name !== room) {
+        return;
+      }
+
+      doc.getConnections().forEach((connection) => {
+        const connectionUserId = connection.request.headers['x-user-id'];
+        if (connectionUserId === userId) {
+          connection.close();
+        }
+      });
+    });
+  }
+
+  res.status(200).json({ message: 'Connections reset' });
+};
diff --git a/src/frontend/servers/y-provider/src/handlers/collaborationWSHandler.ts b/src/frontend/servers/y-provider/src/handlers/collaborationWSHandler.ts
new file mode 100644
index 000000000..f76ef88c6
--- /dev/null
+++ b/src/frontend/servers/y-provider/src/handlers/collaborationWSHandler.ts
@@ -0,0 +1,16 @@
+import { Request } from 'express';
+import * as ws from 'ws';
+
+import { hocusPocusServer } from '@/servers/hocusPocusServer';
+import { logger } from '@/utils';
+
+export const collaborationWSHandler = (ws: ws.WebSocket, req: Request) => {
+  logger('Incoming Origin:', req.headers['origin']);
+
+  try {
+    hocusPocusServer.handleConnection(ws, req);
+  } catch (error) {
+    console.error('Failed to handle WebSocket connection:', error);
+    ws.close();
+  }
+};
diff --git a/src/frontend/servers/y-provider/src/handlers/convertMarkdownHandler.ts b/src/frontend/servers/y-provider/src/handlers/convertMarkdownHandler.ts
new file mode 100644
index 000000000..9fc09134a
--- /dev/null
+++ b/src/frontend/servers/y-provider/src/handlers/convertMarkdownHandler.ts
@@ -0,0 +1,55 @@
+import { ServerBlockNoteEditor } from '@blocknote/server-util';
+import { Request, Response } from 'express';
+import * as Y from 'yjs';
+
+import { logger, toBase64 } from '@/utils';
+
+interface ConversionRequest {
+  content: string;
+}
+
+interface ConversionResponse {
+  content: string;
+}
+
+interface ErrorResponse {
+  error: string;
+}
+
+export const convertMarkdownHandler = async (
+  req: Request<
+    object,
+    ConversionResponse | ErrorResponse,
+    ConversionRequest,
+    object
+  >,
+  res: Response<ConversionResponse | ErrorResponse>,
+) => {
+  const content = req.body?.content;
+
+  if (!content) {
+    res.status(400).json({ error: 'Invalid request: missing content' });
+    return;
+  }
+
+  try {
+    const editor = ServerBlockNoteEditor.create();
+
+    // Perform the conversion from markdown to Blocknote.js blocks
+    const blocks = await editor.tryParseMarkdownToBlocks(content);
+
+    if (!blocks || blocks.length === 0) {
+      res.status(500).json({ error: 'No valid blocks were generated' });
+      return;
+    }
+
+    // Create a Yjs Document from blocks, and encode it as a base64 string
+    const yDocument = editor.blocksToYDoc(blocks, 'document-store');
+    const documentContent = toBase64(Y.encodeStateAsUpdate(yDocument));
+
+    res.status(200).json({ content: documentContent });
+  } catch (e) {
+    logger('conversion failed:', e);
+    res.status(500).json({ error: 'An error occurred' });
+  }
+};
diff --git a/src/frontend/servers/y-provider/src/handlers/index.ts b/src/frontend/servers/y-provider/src/handlers/index.ts
new file mode 100644
index 000000000..75bd7f7bb
--- /dev/null
+++ b/src/frontend/servers/y-provider/src/handlers/index.ts
@@ -0,0 +1,3 @@
+export * from './collaborationResetConnectionsHandler';
+export * from './collaborationWSHandler';
+export * from './convertMarkdownHandler';
diff --git a/src/frontend/servers/y-provider/src/middlewares.ts b/src/frontend/servers/y-provider/src/middlewares.ts
index 48e56895d..30470ecaf 100644
--- a/src/frontend/servers/y-provider/src/middlewares.ts
+++ b/src/frontend/servers/y-provider/src/middlewares.ts
@@ -1,3 +1,4 @@
+import cors from 'cors';
 import { NextFunction, Request, Response } from 'express';
 import * as ws from 'ws';
 
@@ -7,26 +8,20 @@ import {
   Y_PROVIDER_API_KEY,
 } from '@/env';
 
-import { logger } from './utils';
-
 const VALID_API_KEYS = [COLLABORATION_SERVER_SECRET, Y_PROVIDER_API_KEY];
+const allowedOrigins = COLLABORATION_SERVER_ORIGIN.split(',');
+
+export const corsMiddleware = cors({
+  origin: allowedOrigins,
+  methods: ['GET', 'POST'],
+  credentials: true,
+});
 
 export const httpSecurity = (
   req: Request,
   res: Response,
   next: NextFunction,
 ): void => {
-  // Origin check
-  const origin = req.headers['origin'];
-  if (origin && COLLABORATION_SERVER_ORIGIN !== origin) {
-    logger('CORS policy violation: Invalid Origin', origin);
-
-    res
-      .status(403)
-      .json({ error: 'CORS policy violation: Invalid Origin', origin });
-    return;
-  }
-
   // Secret API Key check
   // Note: Changing this header to Bearer token format will break backend compatibility with this microservice.
   const apiKey = req.headers['authorization'];
@@ -45,9 +40,9 @@ export const wsSecurity = (
 ): void => {
   // Origin check
   const origin = req.headers['origin'];
-  if (COLLABORATION_SERVER_ORIGIN !== origin) {
+  if (origin && !allowedOrigins.includes(origin)) {
+    ws.close(4001, 'Origin not allowed');
     console.error('CORS policy violation: Invalid Origin', origin);
-    ws.close();
     return;
   }
 
diff --git a/src/frontend/servers/y-provider/src/server.ts b/src/frontend/servers/y-provider/src/server.ts
deleted file mode 100644
index 1b229950c..000000000
--- a/src/frontend/servers/y-provider/src/server.ts
+++ /dev/null
@@ -1,211 +0,0 @@
-// eslint-disable-next-line import/order
-import './services/sentry';
-import { ServerBlockNoteEditor } from '@blocknote/server-util';
-import { Server } from '@hocuspocus/server';
-import * as Sentry from '@sentry/node';
-import express, { Request, Response } from 'express';
-import expressWebsockets from 'express-ws';
-import * as Y from 'yjs';
-
-import { PORT } from './env';
-import { httpSecurity, wsSecurity } from './middlewares';
-import { routes } from './routes';
-import { logger, toBase64 } from './utils';
-
-export const hocuspocusServer = Server.configure({
-  name: 'docs-y-server',
-  timeout: 30000,
-  quiet: true,
-  onConnect({ requestHeaders, connection, documentName, requestParameters }) {
-    const roomParam = requestParameters.get('room');
-    const canEdit = requestHeaders['x-can-edit'] === 'True';
-
-    if (!canEdit) {
-      connection.readOnly = true;
-    }
-
-    logger(
-      'Connection established:',
-      documentName,
-      'userId:',
-      requestHeaders['x-user-id'],
-      'canEdit:',
-      canEdit,
-      'room:',
-      requestParameters.get('room'),
-    );
-
-    if (documentName !== roomParam) {
-      console.error(
-        'Invalid room name - Probable hacking attempt:',
-        documentName,
-        requestParameters.get('room'),
-        requestHeaders['x-user-id'],
-      );
-
-      return Promise.reject(new Error('Unauthorized'));
-    }
-
-    return Promise.resolve();
-  },
-});
-
-/**
- * init the collaboration server.
- *
- * @param port - The port on which the server listens.
- * @param serverSecret - The secret key for API authentication.
- * @returns An object containing the Express app, Hocuspocus server, and HTTP server instance.
- */
-export const initServer = () => {
-  const { app } = expressWebsockets(express());
-  app.use(express.json());
-
-  /**
-   * Route to handle WebSocket connections
-   */
-  app.ws(routes.COLLABORATION_WS, wsSecurity, (ws, req) => {
-    logger('Incoming Origin:', req.headers['origin']);
-
-    try {
-      hocuspocusServer.handleConnection(ws, req);
-    } catch (error) {
-      console.error('Failed to handle WebSocket connection:', error);
-      ws.close();
-    }
-  });
-
-  type ResetConnectionsRequestQuery = {
-    room?: string;
-  };
-
-  /**
-   * Route to reset connections in a room:
-   *  - If no user ID is provided, close all connections in the room
-   *  - If a user ID is provided, close connections for the user in the room
-   */
-  app.post(
-    routes.COLLABORATION_RESET_CONNECTIONS,
-    httpSecurity,
-    (
-      req: Request<object, object, object, ResetConnectionsRequestQuery>,
-      res: Response,
-    ) => {
-      const room = req.query.room;
-      const userId = req.headers['x-user-id'];
-
-      logger(
-        'Resetting connections in room:',
-        room,
-        'for user:',
-        userId,
-        'room:',
-        room,
-      );
-
-      if (!room) {
-        res.status(400).json({ error: 'Room name not provided' });
-        return;
-      }
-
-      /**
-       * If no user ID is provided, close all connections in the room
-       */
-      if (!userId) {
-        hocuspocusServer.closeConnections(room);
-      } else {
-        /**
-         * Close connections for the user in the room
-         */
-        hocuspocusServer.documents.forEach((doc) => {
-          if (doc.name !== room) {
-            return;
-          }
-
-          doc.getConnections().forEach((connection) => {
-            const connectionUserId = connection.request.headers['x-user-id'];
-            if (connectionUserId === userId) {
-              connection.close();
-            }
-          });
-        });
-      }
-
-      res.status(200).json({ message: 'Connections reset' });
-    },
-  );
-
-  interface ConversionRequest {
-    content: string;
-  }
-
-  interface ConversionResponse {
-    content: string;
-  }
-
-  interface ErrorResponse {
-    error: string;
-  }
-
-  /**
-   * Route to convert markdown
-   */
-  app.post(
-    routes.CONVERT_MARKDOWN,
-    httpSecurity,
-    async (
-      req: Request<
-        object,
-        ConversionResponse | ErrorResponse,
-        ConversionRequest,
-        object
-      >,
-      res: Response<ConversionResponse | ErrorResponse>,
-    ) => {
-      const content = req.body?.content;
-
-      if (!content) {
-        res.status(400).json({ error: 'Invalid request: missing content' });
-        return;
-      }
-
-      try {
-        const editor = ServerBlockNoteEditor.create();
-
-        // Perform the conversion from markdown to Blocknote.js blocks
-        const blocks = await editor.tryParseMarkdownToBlocks(content);
-
-        if (!blocks || blocks.length === 0) {
-          res.status(500).json({ error: 'No valid blocks were generated' });
-          return;
-        }
-
-        // Create a Yjs Document from blocks, and encode it as a base64 string
-        const yDocument = editor.blocksToYDoc(blocks, 'document-store');
-        const documentContent = toBase64(Y.encodeStateAsUpdate(yDocument));
-
-        res.status(200).json({ content: documentContent });
-      } catch (e) {
-        logger('conversion failed:', e);
-        res.status(500).json({ error: 'An error occurred' });
-      }
-    },
-  );
-
-  Sentry.setupExpressErrorHandler(app);
-
-  app.get('/ping', (req, res) => {
-    res.status(200).json({ message: 'pong' });
-  });
-
-  app.use((req, res) => {
-    logger('Invalid route:', req.url);
-    res.status(403).json({ error: 'Forbidden' });
-  });
-
-  const server = app.listen(PORT, () =>
-    console.log('Listening on port :', PORT),
-  );
-
-  return { app, server };
-};
diff --git a/src/frontend/servers/y-provider/src/servers/appServer.ts b/src/frontend/servers/y-provider/src/servers/appServer.ts
new file mode 100644
index 000000000..80077bb8e
--- /dev/null
+++ b/src/frontend/servers/y-provider/src/servers/appServer.ts
@@ -0,0 +1,66 @@
+// eslint-disable-next-line import/order
+import '../services/sentry';
+import * as Sentry from '@sentry/node';
+import express from 'express';
+import expressWebsockets from 'express-ws';
+
+import { PORT } from '../env';
+import {
+  collaborationResetConnectionsHandler,
+  collaborationWSHandler,
+  convertMarkdownHandler,
+} from '../handlers';
+import { corsMiddleware, httpSecurity, wsSecurity } from '../middlewares';
+import { routes } from '../routes';
+import { logger } from '../utils';
+
+/**
+ * init the collaboration server.
+ *
+ * @param port - The port on which the server listens.
+ * @param serverSecret - The secret key for API authentication.
+ * @returns An object containing the Express app, Hocuspocus server, and HTTP server instance.
+ */
+export const initServer = () => {
+  const { app } = expressWebsockets(express());
+  app.use(express.json());
+  app.use(corsMiddleware);
+
+  /**
+   * Route to handle WebSocket connections
+   */
+  app.ws(routes.COLLABORATION_WS, wsSecurity, collaborationWSHandler);
+
+  /**
+   * Route to reset connections in a room:
+   *  - If no user ID is provided, close all connections in the room
+   *  - If a user ID is provided, close connections for the user in the room
+   */
+  app.post(
+    routes.COLLABORATION_RESET_CONNECTIONS,
+    httpSecurity,
+    collaborationResetConnectionsHandler,
+  );
+
+  /**
+   * Route to convert markdown
+   */
+  app.post(routes.CONVERT_MARKDOWN, httpSecurity, convertMarkdownHandler);
+
+  Sentry.setupExpressErrorHandler(app);
+
+  app.get('/ping', (req, res) => {
+    res.status(200).json({ message: 'pong' });
+  });
+
+  app.use((req, res) => {
+    logger('Invalid route:', req.url);
+    res.status(403).json({ error: 'Forbidden' });
+  });
+
+  const server = app.listen(PORT, () =>
+    console.log('App listening on port :', PORT),
+  );
+
+  return { app, server };
+};
diff --git a/src/frontend/servers/y-provider/src/servers/hocusPocusServer.ts b/src/frontend/servers/y-provider/src/servers/hocusPocusServer.ts
new file mode 100644
index 000000000..dc159a8e7
--- /dev/null
+++ b/src/frontend/servers/y-provider/src/servers/hocusPocusServer.ts
@@ -0,0 +1,41 @@
+import { Server } from '@hocuspocus/server';
+
+import { logger } from '@/utils';
+
+export const hocusPocusServer = Server.configure({
+  name: 'docs-collaboration',
+  timeout: 30000,
+  quiet: true,
+  onConnect({ requestHeaders, connection, documentName, requestParameters }) {
+    const roomParam = requestParameters.get('room');
+    const canEdit = requestHeaders['x-can-edit'] === 'True';
+
+    if (!canEdit) {
+      connection.readOnly = true;
+    }
+
+    logger(
+      'Connection established:',
+      documentName,
+      'userId:',
+      requestHeaders['x-user-id'],
+      'canEdit:',
+      canEdit,
+      'room:',
+      requestParameters.get('room'),
+    );
+
+    if (documentName !== roomParam) {
+      console.error(
+        'Invalid room name - Probable hacking attempt:',
+        documentName,
+        requestParameters.get('room'),
+        requestHeaders['x-user-id'],
+      );
+
+      return Promise.reject(new Error('Unauthorized'));
+    }
+
+    return Promise.resolve();
+  },
+});
diff --git a/src/frontend/servers/y-provider/src/servers/index.ts b/src/frontend/servers/y-provider/src/servers/index.ts
new file mode 100644
index 000000000..0dfe0852a
--- /dev/null
+++ b/src/frontend/servers/y-provider/src/servers/index.ts
@@ -0,0 +1,2 @@
+export * from './appServer';
+export * from './hocusPocusServer';
diff --git a/src/frontend/servers/y-provider/src/start-server.ts b/src/frontend/servers/y-provider/src/start-server.ts
index e3d305198..c1dbfa214 100644
--- a/src/frontend/servers/y-provider/src/start-server.ts
+++ b/src/frontend/servers/y-provider/src/start-server.ts
@@ -1,3 +1,3 @@
-import { initServer } from './server';
+import { initServer } from './servers/appServer';
 
 initServer();
diff --git a/src/frontend/servers/y-provider/src/utils.ts b/src/frontend/servers/y-provider/src/utils.ts
index 4eb9e30cd..847c55689 100644
--- a/src/frontend/servers/y-provider/src/utils.ts
+++ b/src/frontend/servers/y-provider/src/utils.ts
@@ -1,9 +1,9 @@
-/* eslint-disable @typescript-eslint/no-unsafe-argument */
-/* eslint-disable @typescript-eslint/no-explicit-any */
 import { COLLABORATION_LOGGING } from './env';
 
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
 export function logger(...args: any[]) {
   if (COLLABORATION_LOGGING === 'true') {
+    // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
     console.log(...args);
   }
 }
diff --git a/src/frontend/yarn.lock b/src/frontend/yarn.lock
index 6301d5776..3b8e24d1e 100644
--- a/src/frontend/yarn.lock
+++ b/src/frontend/yarn.lock
@@ -4495,6 +4495,13 @@
   resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.5.tgz#14a3e83fa641beb169a2dd8422d91c3c345a9a78"
   integrity sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==
 
+"@types/cors@2.8.17":
+  version "2.8.17"
+  resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.17.tgz#5d718a5e494a8166f569d986794e49c48b216b2b"
+  integrity sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==
+  dependencies:
+    "@types/node" "*"
+
 "@types/debug@^4.0.0":
   version "4.1.12"
   resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917"
@@ -6064,6 +6071,14 @@ core-util-is@~1.0.0:
   resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
   integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
 
+cors@2.8.5:
+  version "2.8.5"
+  resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
+  integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
+  dependencies:
+    object-assign "^4"
+    vary "^1"
+
 cosmiconfig@^7.0.0:
   version "7.1.0"
   resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6"
@@ -10224,7 +10239,7 @@ nwsapi@^2.2.12, nwsapi@^2.2.2:
   resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.16.tgz#177760bba02c351df1d2644e220c31dfec8cdb43"
   integrity sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==
 
-object-assign@^4.1.0, object-assign@^4.1.1:
+object-assign@^4, object-assign@^4.1.0, object-assign@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
   integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
@@ -13187,7 +13202,7 @@ value-or-function@^4.0.0:
   resolved "https://registry.yarnpkg.com/value-or-function/-/value-or-function-4.0.0.tgz#70836b6a876a010dc3a2b884e7902e9db064378d"
   integrity sha512-aeVK81SIuT6aMJfNo9Vte8Dw0/FZINGBV8BfCraGtqVxIeLAEhJyoWs8SmvRVmXfGss2PmmOwZCuBPbZR+IYWg==
 
-vary@~1.1.2:
+vary@^1, vary@~1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
   integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==