How to Build a Full-Featured Bookmarks Manager from Scratch: A Comprehensive Guide

In an age where digital clutter is the norm, managing a sprawling collection of online resources efficiently can feel like an uphill battle. Browser-based bookmarking solutions, while convenient, often fall short when it comes to advanced organization, cross-device syncing, and privacy. You might find yourself juggling multiple browsers, losing links across devices, or being forced to rely on third-party services that monetize your data. Building your own bookmarks manager from scratch not only gives you complete control over your data but also allows you to tailor every feature to your exact workflow. From tagging and categorization to full-text search and import/export capabilities, a custom-built solution transforms the way you save, retrieve, and interact with web content. Moreover, the process of building one deepens your understanding of full-stack web development, database design, and API architecture, making it an incredibly rewarding project for developers of all skill levels.

This tutorial will guide you through the entire journey of creating a robust bookmarks manager, from initial planning and technology selection to deployment and advanced features. We will adopt a modern tech stack: Node.js with Express for the backend, React for a dynamic frontend, and MongoDB with Mongoose for flexible data storage. Along the way, we’ll cover essential topics such as user authentication, RESTful API design, full-text search, tag-based filtering, import/export of browser bookmarks, and deployment strategies. By the end, you will have a fully functional, deployable application that you can extend with browser extensions, mobile apps, or any other integrations you desire. Each step is explained in meticulous detail, including code snippets, database schemas, and best practices, ensuring that even beginners can follow along while advanced developers gain new insights. Let’s dive into the world of building your own digital librarian.

Article illustration

Step 1: Planning and Gathering Requirements

Before writing a single line of code, it is crucial to define the scope and features of your bookmarks manager. This planning phase will save you countless hours of rewriting later. Start by listing the core functionalities you need: at a minimum, users must be able to create, read, update, and delete bookmarks. Each bookmark should store a URL, a title, an optional description, and a collection of tags or categories. You might also want to support folders for hierarchical organization, bookmark deduplication, and a powerful search engine that can search through titles, descriptions, and even the content of the bookmarked pages (via crawling or metadata extraction). Additionally, consider user authentication – do you want multiple user accounts, or will it be a single-user tool? For this tutorial, we will implement a multi-user system with JWT-based authentication to demonstrate industry-standard security practices.

Beyond the basic CRUD operations, think about data portability. A good bookmarks manager should allow you to import bookmarks from popular browsers (Chrome, Firefox) using standard HTML export files, and export your collection as JSON or HTML. This ensures you are never locked into a proprietary format. Also plan for features like bookmark previews (fetching og:image and og:title from URLs), analytics (most clicked, recently added), and perhaps a read-later functionality with a “mark as read” toggle. Create user stories: “As a user, I want to tag a bookmark with ‘tutorial’ and ‘JavaScript’ so I can filter later.” “As a user, I want to search for ‘Node.js deployment’ and see results from my entire library.” Document these in a simple text file or a project management tool like Trello. Finally, sketch wireframes for the main views: dashboard, bookmark list, bookmark form, search results, and settings. With a clear roadmap, you can now move to the technical setup.

Step 2: Setting Up the Development Environment

To begin building, you need a cohesive development environment. We’ll use a monorepo structure for simplicity, with separate folders for the backend and frontend. First, ensure you have Node.js (version 18 or later) and npm installed. Then, choose an IDE such as Visual Studio Code with extensions for ESLint, Prettier, and MongoDB syntax highlighting. Create a root directory named bookmarks-manager. Inside, initialize a Git repository for version control. Now, set up the backend: create a folder backend, navigate into it, and run npm init -y. Install the core dependencies: express, mongoose, bcryptjs (for password hashing), jsonwebtoken (for JWT), cors, dotenv, and helmet for security. Also install nodemon as a dev dependency for auto-restarting the server. Your package.json should have a script like "start": "node server.js" and "dev": "nodemon server.js".

For the frontend, create a frontend folder alongside the backend. Use Create React App or Vite – Vite is recommended for faster builds. Run npm create vite@latest ./frontend -- --template react and follow the prompts. Inside the frontend folder, install additional packages: axios for HTTP requests, react-router-dom for routing, and react-toastify for notifications. Set up a .env file in the backend with your MongoDB connection string (use a local instance or MongoDB Atlas free tier). For local development, install MongoDB Community Edition and start the service. Now, create a basic Express server in backend/server.js that listens on port 5000 and connects to MongoDB. Verify that the server starts without errors. This environment is now ready for building the database schema and API endpoints.

Step 3: Designing the Database Schema

MongoDB is an excellent choice for a bookmarks manager because of its flexible document model. We will define three main Mongoose schemas: User, Bookmark, and Tag. The User schema should include fields for email (unique, required), password (hashed), name, and a createdAt timestamp. Use bcryptjs to hash passwords before saving, and add a method to compare passwords during login. The Bookmark schema is the heart of the application. It should contain: user (ObjectId referencing User), url (required, validated with a regex), title (string), description (text, indexed for search), favicon (string for URL), tags (array of ObjectIds referencing Tag), folder (optional ObjectId for hierarchical grouping), isFavorite (boolean), isReadLater (boolean), and createdAt/updatedAt timestamps. Add a compound index on user and url to prevent duplicate bookmarks for the same user.

The Tag schema should be simple: name (string, unique per user), color (optional hex string for UI display), and user reference. To allow efficient querying, you may also embed tags as strings directly in the Bookmark document for faster reads, but using a separate Tag collection with references gives you the ability to rename or delete tags globally. I recommend the referenced approach for scalability. Additionally, consider adding a BookmarkContent schema if you plan to index the actual content of bookmarked pages. This schema would store extracted text, meta description, and images. However, for simplicity, we will rely on the title and description fields. Below is a sample Bookmark schema code snippet:

const bookmarkSchema = new mongoose.Schema({
  user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
  url: { type: String, required: true, validate: { validator: (v) => /^https?:\/\/.+/.test(v), message: 'Invalid URL' } },
  title: { type: String, default: '' },
  description: { type: String, default: '' },
  tags: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Tag' }],
  folder: { type: mongoose.Schema.Types.ObjectId, ref: 'Folder' },
  isFavorite: { type: Boolean, default: false },
  isReadLater: { type: Boolean, default: false },
}, { timestamps: true });
bookmarkSchema.index({ user: 1, url: 1 }, { unique: true });
bookmarkSchema.index({ title: 'text', description: 'text' });

Step 4: Building the Backend API (CRUD + Authentication)

With the database schema in place, we can now build the RESTful API. Create an routes folder and subfolders for auth, bookmarks, tags, and folders. For authentication, implement POST /api/auth/register to hash the password and create a user, returning a JWT token. POST /api/auth/login verifies credentials and returns a token. Protect all other routes with a middleware that verifies the JWT from the Authorization: Bearer header and attaches the user ID to the request object. Use express.Router() and modularize your code.

For bookmarks, create the standard endpoints. GET /api/bookmarks should accept query parameters for search (q for text search), tag filtering (tags[]), folder filtering (folder), pagination (page and limit), and sorting (sortBy with values like ‘createdAt’, ‘title’). Implement a text search using MongoDB’s $text operator on the index we created. For example: Bookmark.find({ user: req.userId, $text: { $search: req.query.q } }). For tag filtering, use $all to ensure all selected tags are present. Pagination can be done with .skip((page-1)*limit).limit(limit). POST /api/bookmarks should validate the URL, fetch the page title and meta description (using a headless browser like puppeteer or a simpler HTML parser with cheerio), and create the document. PUT /api/bookmarks/:id updates fields, and DELETE /api/bookmarks/:id removes the document and also removes the bookmark reference from any tags or folders if needed.

Similarly, implement CRUD for tags and folders. Tags endpoints: GET /api/tags (returns all tags for the user with bookmark count), POST /api/tags (create tag), PUT /api/tags/:id, DELETE /api/tags/:id (also removes tag references from bookmarks). For folders, a hierarchical structure may require a parent reference; implement GET /api/folders to return a tree structure using recursion. Use middleware for error handling and validation (use express-validator package for input validation). Also, implement rate limiting to prevent abuse. Test all endpoints using Postman or a similar client before moving to the frontend.

Step 5: Creating the Frontend User Interface

Now we bring the application to life with React. Start by setting up the project structure: src/components for presentational components like BookmarkCard, TagChip, SearchBar, Pagination; src/pages for page-level components like Dashboard, LoginPage, RegisterPage; src/services for API calls using Axios; and src/context for authentication state using React Context. Install react-router-dom and set up routes: / (dashboard), /login, /register, /bookmark/new, /bookmark/:id/edit. Use private route wrappers to redirect unauthenticated users to login.

Create a global AuthContext that holds the user token and user info. On app load, check if a token exists in localStorage and validate it with a call to /api/auth/me. If invalid, redirect to login. For the dashboard, fetch bookmarks on component mount using useEffect with the current search and filter state. Display bookmarks in a grid or list format using BookmarkCard components. Each card shows the favicon, title, URL (truncated), description snippet, tags as colored chips, and action buttons (edit, delete, favorite, read later). Use react-toastify to show success or error messages after API calls. Implement a search bar that triggers a debounced API call as the user types. Use useCallback and useMemo for performance optimization. For creating or editing a bookmark, use a modal or a separate page with a form that includes fields for URL, title, description, tag selection (multi-select dropdown), and folder selector. Fetch available tags and folders when the form opens.

For tag management, create a dedicated TagsPage where users can add, rename, delete tags and see how many bookmarks each tag has. For folders, implement a drag-and-drop tree using a library like react-beautiful-dnd to reorganize folders. Ensure that the UI is responsive using CSS modules or Tailwind CSS. For styling, I recommend Tailwind because it allows rapid prototyping and consistency. Use utility classes for spacing, colors, and typography. Also, add dark mode support using Tailwind’s dark variant and a context toggle. The overall design should be clean and minimal, focusing on readability and quick access to bookmarks.

Step 6: Implementing Search, Filtering, and Import/Export

A bookmarks manager is only as good as its search capabilities. We already set up text search in the backend, but we need to fine-tune it. In the frontend, create a search input that sends the query to the API with a debounce of 300ms. Add filter controls: a multi-select dropdown for tags, a dropdown for folders, a checkbox for “favorites only”, and a sort selector (by date, title, domain). These filters should be combined into a query string. For example: ?q=node.js&tags[]=tutorial&tags[]=backend&folder=123&isFavorite=true&sortBy=title. The backend should handle this by building a dynamic MongoDB query using the $and operator. Also implement a “clear filters” button.

Import functionality is essential for migrating from existing browsers. Standard browser bookmark export files are HTML with a DT (Description List) format. Use a library like cheerio in the backend to parse the uploaded HTML file. Create an endpoint POST /api/import that accepts a multipart form with the file. Parse the HTML, extract URLs, titles, folder structure, and optionally creation dates (if available in the export). For each bookmark, check for duplicates (by URL and user), then insert new ones. You can also recreate folder hierarchy. For export, create GET /api/export that generates an HTML file in the same Netscape Bookmark Format, or a simpler JSON file. Respond with a downloadable file. Include a progress indicator in the frontend for large imports.

Step 7: Adding Advanced Features (Metadata Fetching, Deduplication, Readability)

To make your bookmarks manager truly powerful, enhance it with automatic metadata fetching. When a user adds a bookmark, have the backend scrape the URL for og:title, og:description, and og:image (favicon). Use Axios to fetch the page HTML, then parse it with cheerio. If the OG tags are missing, fallback to the </code> tag and first paragraph. Store this data in the bookmark document. Additionally, implement a “readability” feature using <code>@mozilla/readability</code> library to extract the main article content from the page and save it as text in a separate collection. This allows full-text search of the page content without crawling again. Host a scheduled job (e.g., using node-cron) that refreshes metadata for bookmarks older than a month.</p> <p>Deduplication is crucial. Aside from the unique index on (user, url), we can add a client-side check: before creating a bookmark, the frontend can query the API for existing bookmarks with the same URL and notify the user. You can also offer a “merge duplicates” feature that combines tags from multiple entries. Another advanced feature is “bookmark health checking”: periodically ping all bookmarked URLs and mark those that return 404 or timeout. Use a background worker with a queue system like Bull (Redis-based) to do this efficiently without blocking the main API.</p> <h2>Step 8: Deployment and Hosting</h2> <p>With the application fully functional, it’s time to deploy it to the cloud. For the backend, you can use platforms like <strong>Heroku</strong>, <strong>DigitalOcean App Platform</strong>, or <strong>AWS Elastic Beanstalk</strong>. We’ll use Heroku for simplicity. First, create a <code>Procfile</code> in the backend root with <code>web: node server.js</code>. Set environment variables in Heroku: <code>MONGODB_URI</code> (use MongoDB Atlas), <code>JWT_SECRET</code>, <code>NODE_ENV=production</code>. Push your code to a GitHub repository and connect it to Heroku for automatic deploys. For the frontend, build the React app into static files with <code>npm run build</code>. Then serve these files from the backend in production: configure Express to serve the <code>frontend/build</code> folder as static, and add a catch-all route that returns <code>index.html</code> for any non-API routes. This way, your entire app runs on a single domain, simplifying CORS and deployment.</p> <p>Alternatively, you can deploy the frontend separately on <strong>Netlify</strong> or <strong>Vercel</strong> and point the API to a backend URL. Use environment variables in the frontend build to set the API base URL. For security, ensure your production MongoDB cluster has IP whitelist only for the backend server. Set up SSL using Heroku’s automatic certificate or a custom domain with Let’s Encrypt. Finally, set up monitoring with tools like Sentry for error tracking and LogRocket for session replay. After deployment, run a thorough test of all features: user registration, login, bookmark CRUD, search, import/export, and mobile responsiveness. Your bookmarks manager is now live and fully functional.</p> <h2>Tips and Best Practices</h2> <h3>1. Implement Proper Input Validation and Sanitization</h3> <p>Since users will be entering arbitrary URLs and text, always validate and sanitize inputs to prevent injection attacks and data corruption. Use <code>express-validator</code> on the backend to check URL format, string lengths, and allowed characters. For example, enforce that tag names are alphanumeric with underscores, and strip any HTML tags from descriptions. On the frontend, use libraries like <code>validator</code> for real-time validation. Additionally, sanitize URLs before storing: remove trailing slashes, convert to lowercase, and strip tracking parameters (e.g., <code>utm_source</code>). This will improve search accuracy and reduce duplicate entries. Also, be cautious with user-supplied HTML in bookmark descriptions; render it safely using <code>dangerouslySetInnerHTML</code> only after sanitizing with DOMPurify.</p> <h3>2. Optimize Database Queries for Performance</h3> <p>As your bookmark collection grows (potentially thousands of entries), poor database queries can slow down the app. Use MongoDB indexes strategically: we already created a text index on title and description, and a unique compound index on user+url. Additionally, index fields used in filtering: <code>tags</code>, <code>folder</code>, <code>isFavorite</code>, <code>createdAt</code>. Use the explain() method to analyze query performance. For pagination, avoid large offsets; use cursor-based pagination with <code>$gt</code> on createdAt if you need real-time infinite scroll. For full-text search, consider using MongoDB Atlas Search (which uses Lucene) for more advanced features like fuzzy matching and autocomplete. Also, implement caching for frequently accessed data like tag lists and folder trees using Redis. This reduces load on the primary database and speeds up the UI.</p> <h3>3. Design for Extensibility with a Clean Architecture</h3> <p>Think ahead about potential future features: browser extension, mobile app, or integration with other services (Pocket, Raindrop). Structure your backend code with a layered architecture: routes, controllers, services, and models. Keep business logic in services, not in controllers. For example, a <code>bookmarkService.js</code> can contain functions like <code>createBookmark</code> with duplicate checking, metadata fetching, and tag autocomplete. This makes unit testing easier and allows you to reuse logic in a future CLI tool. Use environment variables for all configuration (API keys, database URIs). For the frontend, use a state management library like Zustand or Redux Toolkit to keep track of UI state, API loading, and error handling separately from components. This architecture ensures that adding a new feature (e.g., adding a “shareable link” feature) does not require rewriting existing code.</p> <h2>FAQ Section</h2> <h3>Q1: What tech stack is best for building a bookmarks manager?</h3> <p>There is no single “best” stack – it depends on your skills and requirements. The stack we used (Node.js, Express, MongoDB, React) is excellent for rapid development and scalability. If you prefer a typed language, consider TypeScript on both frontend and backend, or go with a Python backend (Django or FastAPI) and a PostgreSQL database. For a simpler, serverless approach, you could use Firebase (Firestore) and Next.js with Vercel. However, for a self-hosted solution with full control, the MERN stack is a proven combination. If you want to support billions of bookmarks, consider scaling with a search engine like Elasticsearch for text search and a separate key-value store for counters. The important thing is to choose a stack you are comfortable with, as building a full-featured manager requires substantial work.</p> <h3>Q2: How do I handle large numbers of bookmarks (100,000+) without performance issues?</h3> <p>Scaling to hundreds of thousands of bookmarks requires careful planning. First, optimize your database schema with proper indexing – ideally a compound index for user-specific queries. Use sharding in MongoDB if you anticipate massive growth. For search, offload to a dedicated search engine like MeiliSearch or Elasticsearch. Use pagination and limit the number of results returned per request (e.g., 20-50). For the frontend, implement virtual scrolling using <code>react-window</code> or <code>react-virtuoso</code> to render only visible bookmarks. Also, consider lazy-loading bookmark metadata – only fetch the title and URL for list views, and fetch descriptions only when a user opens a bookmark detail. Use Redis to cache frequent queries (e.g., “user’s recent bookmarks”) with a short TTL. Finally, run database maintenance tasks like compacting collections and removing deleted entries regularly.</p> <h3>Q3: Can I sync bookmarks across multiple devices?</h3> <p>Yes, by design your bookmarks manager is server-based, so any device with internet access can sync. The backend serves as the single source of truth. After deploying the backend to a cloud server, users can log in from any device (browser, mobile app, browser extension) and see the same data. To ensure a smooth experience, implement optimistic UI updates on the frontend so that changes appear instantly while being synced in the background. Use WebSockets or Server-Sent Events to push updates to all connected clients when a bookmark is modified from one device. For offline support, you can consider using IndexedDB in the browser to cache a local copy and sync when the connection is restored. This is especially useful for mobile apps built with React Native or PWA.</p> <h3>Q4: How do I build a browser extension for my bookmarks manager?</h3> <p>A browser extension can greatly enhance usability by allowing one-click bookmarking. For Chrome, you’ll need a <code>manifest.json</code>, a background script, and a popup HTML. The extension can communicate with your backend API using the same JWT token stored in the extension’s storage. Implement features like: add bookmark with current tab URL and title, list recent bookmarks in the popup, and a search bar. Use the <code>chrome.tabs</code> API to get the current tab. For cross-browser compatibility, use the WebExtensions API (works on Firefox, Edge, Opera). Publish the extension on Chrome Web Store or Firefox Add-ons. You can also add context menu options to save a link directly. The backend already has the necessary API endpoints, so the extension is just a thin client.</p> <h3>Q5: Is it better to use a database or a file-based storage (like JSON files) for bookmarks?</h3> <p>For a single-user, local application, a file-based approach (JSON, SQLite) can be simpler and avoid the overhead of setting up a database server. However, if you want multi-user, web-based access, syncing, and advanced search, a proper database is essential. MongoDB offers flexibility (schemaless for different bookmark types) and powerful querying. SQLite embedded in Node.js works fine for local desktop apps (using Electron). For a production web app, PostgreSQL or MySQL provide strong data integrity and relational features. File-based storage becomes problematic when you need concurrent write access, data consistency, and complex queries. My recommendation: use a database from the start, even for local projects, because it scales and makes future enhancements (like mobile apps) much easier.</p> <h2>Conclusion</h2> <p>Building a bookmarks manager from scratch is a challenging yet immensely satisfying project that combines frontend artistry with backend engineering. Throughout this guide, we have laid out a clear path from concept to deployment, covering everything from planning and database design to advanced search, import/export, and browser extension integration. You now have the knowledge to create a robust, feature-rich application that gives you full control over your digital resources. Remember that the beauty of open-source development is in continuous improvement – add features like AI-powered tagging, link previews, or collaborative sharing. Share your code on GitHub, contribute to the community, and keep refining your manager to match your evolving needs. The skills you’ve honed in this journey – API design, authentication, database optimization, and responsive UI development – are directly transferable to other web projects. So, open your editor, start coding, and transform the way you manage bookmarks forever.</p> <h2>Reference Tables</h2> <h3>Database Technologies Comparison</h3> <table border="1" cellpadding="5" style="border-collapse: collapse; width: 100%;"> <thead> <tr> <th>Database</th> <th>Type</th> <th>Strengths</th> <th>Weaknesses</th> <th>Best For</th> </tr> </thead> <tbody> <tr> <td>MongoDB</td> <td>Document (NoSQL)</td> <td>Flexible schema, easy to scale horizontally, built-in text search</td> <td>Less mature ACID transactions, requires memory for indexes</td> <td>Rapid prototyping, hierarchical data, large-scale web apps</td> </tr> <tr> <td>PostgreSQL</td> <td>Relational (SQL)</td> <td>Strong ACID compliance, advanced indexing (GIN, GiST), JSON support</td> <td>Steeper learning curve for document-oriented data</td> <td>Complex queries, data integrity, analytics</td> </tr> <tr> <td>SQLite</td> <td>Embedded (SQL)</td> <td>Zero configuration, portable, no server overhead</td> <td>Limited concurrency, not suitable for multi-user web apps</td> <td>Local desktop apps, single-user tools, prototypes</td> </tr> <tr> <td>Elasticsearch</td> <td>Search engine</td> <td>Blazing full-text search, real-time analytics, scalable</td> <td>Not a primary data store, requires separate database for persistence</td> <td>Full-text search, autocomplete, logs</td> </tr> </tbody> </table> <h3>Frontend Framework Comparison for Bookmark Manager UI</h3> <table border="1" cellpadding="5" style="border-collapse: collapse; width: 100%;"> <thead> <tr> <th>Framework</th> <th>Rendering</th> <th>State Management</th> <th>Learning Curve</th> <th>Ecosystem</th> <th>Best For</th> </tr> </thead> <tbody> <tr> <td>React</td> <td>Virtual DOM</td> <td>Context, Redux, Zustand</td> <td>Moderate</td> <td>Huge library ecosystem, strong community</td> <td>Complex SPAs, mobile via React Native</td> </tr> <tr> <td>Vue</td> <td>Virtual DOM</td> <td>Vuex, Pinia</td> <td>Low</td> <td>Growing ecosystem, excellent documentation</td> <td>Rapid development, smaller projects, progressive enhancement</td> </tr> <tr> <td>Svelte</td> <td>Compile-time (no virtual DOM)</td> <td>Built-in stores</td> <td>Low</td> <td>Smaller but active community</td> <td>Lightweight apps, high performance, minimal bundle size</td> </tr> <tr> <td>Next.js (React)</td> <td>SSR/SSG + client-side</td> <td>Same as React</td> <td>Moderate-High</td> <td>Excellent for SEO, built-in routing, API routes</td> <td>Static+dynamic hybrid apps, SEO-critical apps</td> </tr> </tbody> </table> <div class="uwp_widgets uwp_widget_author_box bsui sdel-9a8e25eb" ><div class="d-block text-center text-md-start d-md-flex p-3 bg-light"> <a href="https://sumberlaba.com/index.php/profile/claw100/"><img decoding="async" src="https://secure.gravatar.com/avatar/af7fe42f3affb1ab210b7e7b693d73cf15ac3e49f357d96f1a98b9ddeaded070?s=96&r=g&d=https://sumberlaba.com/wp-content/plugins/userswp/assets/images/no_profile.png" class="rounded-circle shadow border border-white border-width-4 me-3" width="60" height="60" alt="sarah antaboga"></a> <div class="media-body"> <h5 class="mt-0">Author: <a href="https://sumberlaba.com/index.php/profile/claw100/">sarah antaboga</a></h5> <p></p> </div> </div></div> </div> <!-- .entry-content --> </article><!-- #post-## --> <nav class="navigation post-navigation" aria-label="Posts"> <h2 class="screen-reader-text">Post navigation</h2> <div class="nav-links"><div class="nav-previous"><a href="https://sumberlaba.com/index.php/2026/07/02/understanding-explainable-ai-xai-a-comprehensive-guide-to-transparent-machine-learning/" rel="prev">Understanding Explainable AI (XAI): A Comprehensive Guide to Transparent Machine Learning</a></div><div class="nav-next"><a href="https://sumberlaba.com/index.php/2026/07/02/how-to-use-jupyter-notebook-a-complete-beginners-guide-to-interactive-python-coding/" rel="next">How to Use Jupyter Notebook: A Complete Beginner’s Guide to Interactive Python Coding</a></div></div> </nav> <div id="comments" class="comments-area"> <div class="comments-area"> <div id="respond" class="comment-respond"> <h3 id="reply-title" class="comment-reply-title">Leave a Reply <small><a rel="nofollow" id="cancel-comment-reply-link" href="/index.php/2026/07/02/how-to-build-a-full-featured-bookmarks-manager-from-scratch-a-comprehensive-guide/#respond" style="display:none;">Cancel reply</a></small></h3><form action="https://sumberlaba.com/wp-comments-post.php" method="post" id="commentform" class="comment-form"><p class="comment-notes"><span id="email-notes">Your email address will not be published.</span> <span class="required-field-message">Required fields are marked <span class="required">*</span></span></p><p class="comment-form-comment"><label class="screen-reader-text" for="comment">Comment</label><textarea id="comment" name="comment" placeholder="Comment" cols="45" rows="8" aria-required="true" required></textarea></p><p class="comment-form-author"><label class="screen-reader-text" for="author">Name<span class="required">*</span></label><input id="author" name="author" placeholder="Name*" type="text" value="" size="30" aria-required='true' required /></p> <p class="comment-form-email"><label class="screen-reader-text" for="email">Email<span class="required">*</span></label><input id="email" name="email" placeholder="Email*" type="text" value="" size="30" aria-required='true' required /></p> <p class="comment-form-url"><label class="screen-reader-text" for="url">Website</label><input id="url" name="url" placeholder="Website" type="text" value="" size="30" /></p> <p class="comment-form-cookies-consent"><input id="wp-comment-cookies-consent" name="wp-comment-cookies-consent" type="checkbox" value="yes" /> <label for="wp-comment-cookies-consent">Save my name, email, and website in this browser for the next time I comment.</label></p> <p class="form-submit"><input name="submit" type="submit" id="submit" class="submit" value="Post Comment" /> <input type='hidden' name='comment_post_ID' value='956' id='comment_post_ID' /> <input type='hidden' name='comment_parent' id='comment_parent' value='0' /> </p><p style="display: none !important;" class="akismet-fields-container" data-prefix="ak_"><label>Δ<textarea name="ak_hp_textarea" cols="45" rows="8" maxlength="100"></textarea></label><input type="hidden" id="ak_js_1" name="ak_js" value="66"/><script>document.getElementById( "ak_js_1" ).setAttribute( "value", ( new Date() ).getTime() );</script></p></form> </div><!-- #respond --> </div> </main><!-- #main --> </div><!-- #primary --> <aside id="secondary" class="widget-area" role="complementary"> </aside> <aside id="secondary" class="widget-area" role="complementary"> <!-- Search --> <aside id="search" class="widget widget_search" aria-label="firstsidebar"> <h2 class="widget-title">Search</h2> <form method="get" class="search-form" action="https://sumberlaba.com/"> <label> <span class="screen-reader-text">Search for:</span> <input type="search" class="search-field" placeholder="Search..." value="" name="s"> </label> <button type="submit" class="search-submit"></button> </form> </aside> <!-- Archive --> <aside id="archive" class="widget widget_archive" role="complementary" aria-label="secondsidebar"> <h2 class="widget-title">Archive List</h2> <ul> <li><a href='https://sumberlaba.com/index.php/2026/07/'>July 2026</a></li> <li><a href='https://sumberlaba.com/index.php/2026/06/'>June 2026</a></li> <li><a href='https://sumberlaba.com/index.php/2026/05/'>May 2026</a></li> <li><a href='https://sumberlaba.com/index.php/2025/08/'>August 2025</a></li> <li><a href='https://sumberlaba.com/index.php/2024/12/'>December 2024</a></li> <li><a href='https://sumberlaba.com/index.php/2024/08/'>August 2024</a></li> <li><a href='https://sumberlaba.com/index.php/2024/06/'>June 2024</a></li> <li><a href='https://sumberlaba.com/index.php/2024/05/'>May 2024</a></li> <li><a href='https://sumberlaba.com/index.php/2024/04/'>April 2024</a></li> </ul> </aside> <!-- Recent Posts --> <aside id="recent-posts" class="widget widget_recent_posts" role="complementary" aria-label="thirdsidebar"> <h2 class="widget-title">Recent Posts</h2> <ul> <li><a href="https://sumberlaba.com/index.php/2026/07/02/neuro-inspired-computing-a-comprehensive-guide-to-neuromorphic-computing-technology/">Neuro-Inspired Computing: A Comprehensive Guide to Neuromorphic Computing Technology</a></li> <li><a href="https://sumberlaba.com/index.php/2026/07/02/mastering-ai-based-image-editing-a-comprehensive-guide-to-the-best-tools-and-techniques/">Mastering AI-Based Image Editing: A Comprehensive Guide to the Best Tools and Techniques</a></li> <li><a href="https://sumberlaba.com/index.php/2026/07/02/how-to-build-a-social-media-scheduler-a-step-by-step-guide-for-content-creators-and-marketers/">How to Build a Social Media Scheduler: A Step-by-Step Guide for Content Creators and Marketers</a></li> <li><a href="https://sumberlaba.com/index.php/2026/07/02/understanding-consensus-algorithms-in-blockchain-a-comprehensive-guide-to-how-networks-agree/">Understanding Consensus Algorithms in Blockchain: A Comprehensive Guide to How Networks Agree</a></li> <li><a href="https://sumberlaba.com/index.php/2026/07/02/the-ultimate-guide-to-the-best-tools-for-code-obfuscation-in-2025/">The Ultimate Guide to the Best Tools for Code Obfuscation in 2025</a></li> </ul> </aside> <!-- Categories --> <aside id="categories" class="widget widget_categories" role="complementary" aria-label="fourthsidebar"> <h2 class="widget-title">Categories</h2> <ul> <li class="cat-item cat-item-6"><a href="https://sumberlaba.com/index.php/category/blog/">Blog</a> <ul class='children'> <li class="cat-item cat-item-7"><a href="https://sumberlaba.com/index.php/category/blog/business/">Business</a> </li> <li class="cat-item cat-item-11"><a href="https://sumberlaba.com/index.php/category/blog/education/">Education</a> </li> <li class="cat-item cat-item-28"><a href="https://sumberlaba.com/index.php/category/blog/technology/">Technology</a> </li> </ul> </li> <li class="cat-item cat-item-1"><a href="https://sumberlaba.com/index.php/category/non-category/">non category</a> </li> </ul> </aside> </aside> </div> </div> </div> </div> <footer class="site-footer" style="background-color: #632729;"> <div class="footer-t"> <div class="container"> <div class="row"> <!-- Archive --> <aside id="archive" class="widget widget_archive col" role="complementary" aria-label="secondsidebar"> <h2 class="widget-title">Archive List</h2> <ul> <li><a href='https://sumberlaba.com/index.php/2026/07/'>July 2026</a></li> <li><a href='https://sumberlaba.com/index.php/2026/06/'>June 2026</a></li> <li><a href='https://sumberlaba.com/index.php/2026/05/'>May 2026</a></li> <li><a href='https://sumberlaba.com/index.php/2025/08/'>August 2025</a></li> <li><a href='https://sumberlaba.com/index.php/2024/12/'>December 2024</a></li> <li><a href='https://sumberlaba.com/index.php/2024/08/'>August 2024</a></li> <li><a href='https://sumberlaba.com/index.php/2024/06/'>June 2024</a></li> <li><a href='https://sumberlaba.com/index.php/2024/05/'>May 2024</a></li> <li><a href='https://sumberlaba.com/index.php/2024/04/'>April 2024</a></li> </ul> </aside> <!-- Recent Posts --> <aside id="recent-posts" class="widget widget_recent_posts col" role="complementary" aria-label="thirdsidebar"> <h2 class="widget-title">Recent Posts</h2> <ul> <li><a href="https://sumberlaba.com/index.php/2026/07/02/neuro-inspired-computing-a-comprehensive-guide-to-neuromorphic-computing-technology/">Neuro-Inspired Computing: A Comprehensive Guide to Neuromorphic Computing Technology</a></li> <li><a href="https://sumberlaba.com/index.php/2026/07/02/mastering-ai-based-image-editing-a-comprehensive-guide-to-the-best-tools-and-techniques/">Mastering AI-Based Image Editing: A Comprehensive Guide to the Best Tools and Techniques</a></li> <li><a href="https://sumberlaba.com/index.php/2026/07/02/how-to-build-a-social-media-scheduler-a-step-by-step-guide-for-content-creators-and-marketers/">How to Build a Social Media Scheduler: A Step-by-Step Guide for Content Creators and Marketers</a></li> <li><a href="https://sumberlaba.com/index.php/2026/07/02/understanding-consensus-algorithms-in-blockchain-a-comprehensive-guide-to-how-networks-agree/">Understanding Consensus Algorithms in Blockchain: A Comprehensive Guide to How Networks Agree</a></li> <li><a href="https://sumberlaba.com/index.php/2026/07/02/the-ultimate-guide-to-the-best-tools-for-code-obfuscation-in-2025/">The Ultimate Guide to the Best Tools for Code Obfuscation in 2025</a></li> </ul> </aside> <!-- Categories --> <aside id="categories" class="widget widget_categories col" role="complementary" aria-label="fourthsidebar"> <h2 class="widget-title">Categories</h2> <ul> <li class="cat-item cat-item-6"><a href="https://sumberlaba.com/index.php/category/blog/">Blog</a> <ul class='children'> <li class="cat-item cat-item-7"><a href="https://sumberlaba.com/index.php/category/blog/business/">Business</a> </li> <li class="cat-item cat-item-11"><a href="https://sumberlaba.com/index.php/category/blog/education/">Education</a> </li> <li class="cat-item cat-item-28"><a href="https://sumberlaba.com/index.php/category/blog/technology/">Technology</a> </li> </ul> </li> <li class="cat-item cat-item-1"><a href="https://sumberlaba.com/index.php/category/non-category/">non category</a> </li> </ul> </aside> <!-- Tags Widget --> <aside id="tags" class="widget widget_tags col" role="complementary" aria-label="fifthsidebar"> <h2 class="widget-title">Tags</h2> <div class="tag-cloud"> <a href="https://sumberlaba.com/index.php/tag/adobe-photoshop/" class="tag-cloud-link tag-link-30 tag-link-position-1" style="font-size: 8pt;" aria-label="Adobe Photoshop (1 item)">Adobe Photoshop</a> <a href="https://sumberlaba.com/index.php/tag/affiliate-marketing/" class="tag-cloud-link tag-link-25 tag-link-position-2" style="font-size: 8pt;" aria-label="AFFILIATE MARKETING (1 item)">AFFILIATE MARKETING</a> <a href="https://sumberlaba.com/index.php/tag/anresangsia/" class="tag-cloud-link tag-link-34 tag-link-position-3" style="font-size: 8pt;" aria-label="Anresangsia (1 item)">Anresangsia</a> <a href="https://sumberlaba.com/index.php/tag/bisnis/" class="tag-cloud-link tag-link-9 tag-link-position-4" style="font-size: 22pt;" aria-label="Bisnis (6 items)">Bisnis</a> <a href="https://sumberlaba.com/index.php/tag/budi-pekerti/" class="tag-cloud-link tag-link-14 tag-link-position-5" style="font-size: 8pt;" aria-label="Budi Pekerti (1 item)">Budi Pekerti</a> <a href="https://sumberlaba.com/index.php/tag/desain-grafis/" class="tag-cloud-link tag-link-29 tag-link-position-6" style="font-size: 8pt;" aria-label="Desain Grafis (1 item)">Desain Grafis</a> <a href="https://sumberlaba.com/index.php/tag/dharma-wacana/" class="tag-cloud-link tag-link-12 tag-link-position-7" style="font-size: 8pt;" aria-label="Dharma Wacana (1 item)">Dharma Wacana</a> <a href="https://sumberlaba.com/index.php/tag/internet/" class="tag-cloud-link tag-link-8 tag-link-position-8" style="font-size: 8pt;" aria-label="Internet (1 item)">Internet</a> <a href="https://sumberlaba.com/index.php/tag/jasa/" class="tag-cloud-link tag-link-22 tag-link-position-9" style="font-size: 8pt;" aria-label="jasa (1 item)">jasa</a> <a href="https://sumberlaba.com/index.php/tag/komputer/" class="tag-cloud-link tag-link-31 tag-link-position-10" style="font-size: 8pt;" aria-label="Komputer (1 item)">Komputer</a> <a href="https://sumberlaba.com/index.php/tag/landing-page/" class="tag-cloud-link tag-link-20 tag-link-position-11" style="font-size: 8pt;" aria-label="landing page (1 item)">landing page</a> <a href="https://sumberlaba.com/index.php/tag/marketing/" class="tag-cloud-link tag-link-27 tag-link-position-12" style="font-size: 8pt;" aria-label="marketing (1 item)">marketing</a> <a href="https://sumberlaba.com/index.php/tag/online/" class="tag-cloud-link tag-link-26 tag-link-position-13" style="font-size: 12.581818181818pt;" aria-label="online (2 items)">online</a> <a href="https://sumberlaba.com/index.php/tag/pemasaran-afiliasi/" class="tag-cloud-link tag-link-23 tag-link-position-14" style="font-size: 8pt;" aria-label="PEMASARAN AFILIASI (1 item)">PEMASARAN AFILIASI</a> <a href="https://sumberlaba.com/index.php/tag/pembelajaran-daring/" class="tag-cloud-link tag-link-37 tag-link-position-15" style="font-size: 8pt;" aria-label="Pembelajaran Daring (1 item)">Pembelajaran Daring</a> <a href="https://sumberlaba.com/index.php/tag/pembelajaran-jarak-jauh/" class="tag-cloud-link tag-link-39 tag-link-position-16" style="font-size: 8pt;" aria-label="Pembelajaran Jarak Jauh (1 item)">Pembelajaran Jarak Jauh</a> <a href="https://sumberlaba.com/index.php/tag/pendidikan/" class="tag-cloud-link tag-link-13 tag-link-position-17" style="font-size: 12.581818181818pt;" aria-label="Pendidikan (2 items)">Pendidikan</a> <a href="https://sumberlaba.com/index.php/tag/persatuan/" class="tag-cloud-link tag-link-35 tag-link-position-18" style="font-size: 8pt;" aria-label="Persatuan (1 item)">Persatuan</a> <a href="https://sumberlaba.com/index.php/tag/produk/" class="tag-cloud-link tag-link-21 tag-link-position-19" style="font-size: 8pt;" aria-label="produk (1 item)">produk</a> <a href="https://sumberlaba.com/index.php/tag/produk-digital/" class="tag-cloud-link tag-link-33 tag-link-position-20" style="font-size: 8pt;" aria-label="produk digital (1 item)">produk digital</a> <a href="https://sumberlaba.com/index.php/tag/ruang-belajar-zaman-now/" class="tag-cloud-link tag-link-40 tag-link-position-21" style="font-size: 8pt;" aria-label="Ruang Belajar Zaman Now (1 item)">Ruang Belajar Zaman Now</a> <a href="https://sumberlaba.com/index.php/tag/script/" class="tag-cloud-link tag-link-32 tag-link-position-22" style="font-size: 8pt;" aria-label="script (1 item)">script</a> <a href="https://sumberlaba.com/index.php/tag/sistem-pembelajaran/" class="tag-cloud-link tag-link-38 tag-link-position-23" style="font-size: 8pt;" aria-label="Sistem Pembelajaran (1 item)">Sistem Pembelajaran</a> </div> </aside> </div> </div> </div> <div class="site-info"><div class="container"><span class="copyright">© 2026 <a href="https://sumberlaba.com/">sumberlaba.com</a>. All Rights Reserved.</span><span class="by"> Prime Education By <a href="https://themeignite.com/" rel="nofollow" target="_blank">Themeignite</a>. Powered By <a href="https://wordpress.org/" target="_blank">WordPress</a>.<a class="privacy-policy-link" href="https://sumberlaba.com/index.php/kebijakan-privasi/" rel="privacy-policy">Privacy</a></span></div></div> <a id="button"><i class="fas fa-arrow-up"></i></a> </footer> </div> </div> <script type="speculationrules"> {"prefetch":[{"source":"document","where":{"and":[{"href_matches":"/*"},{"not":{"href_matches":["/wp-*.php","/wp-admin/*","/wp-content/uploads/*","/wp-content/*","/wp-content/plugins/*","/wp-content/themes/prime-education/*","/*\\?(.+)"]}},{"not":{"selector_matches":"a[rel~=\"nofollow\"]"}},{"not":{"selector_matches":".no-prefetch, .no-prefetch a"}}]},"eagerness":"conservative"}]} </script> <style>html{font-size:16px;}</style><script id="swv-js" src="https://sumberlaba.com/wp-content/plugins/contact-form-7/includes/swv/js/index.js?ver=5.9.3"></script> <script id="contact-form-7-js-extra"> var wpcf7 = {"api":{"root":"https://sumberlaba.com/index.php/wp-json/","namespace":"contact-form-7/v1"}}; //# sourceURL=contact-form-7-js-extra </script> <script id="contact-form-7-js" src="https://sumberlaba.com/wp-content/plugins/contact-form-7/includes/js/index.js?ver=5.9.3"></script> <script id="uwp_recaptcha_js_api-js-extra"> var uwp_recaptcha_data = {"site_key":"6Lc20xgtAAAAAIEmXm-_iUXFfF_nD65LTjKm8jk_","captcha_version":"default","captcha_theme":"light","ajaxurl":"https://sumberlaba.com/wp-admin/admin-ajax.php"}; //# sourceURL=uwp_recaptcha_js_api-js-extra </script> <script id="uwp_recaptcha_js_api-js" src="https://www.recaptcha.net/recaptcha/api.js?onload=uwp_init_recaptcha&hl=en&render=explicit&ver=1.3.19"></script> <script id="uwp_recaptcha_js_api-js-after"> function uwp_init_recaptcha() { if ( jQuery('.uwp-captcha-render').length) { jQuery('.uwp-captcha-render').each(function() { if(jQuery(this).html()==''){ var container = jQuery(this).attr('id'); if (container) { try { eval(container + '()'); } catch(err) { console.log(err); } } } }); } } function uwp_reset_captcha(element){ if(uwp_recaptcha_data.captcha_version == 'v3') { if (typeof grecaptcha != 'undefined') { holderId = grecaptcha.execute(uwp_recaptcha_data.site_key, {action: 'uwp_captcha'}).then(function (token) { document.getElementById(element).value = token; }); } } else { if (typeof grecaptcha != 'undefined') { grecaptcha.reset(); } } } //# sourceURL=uwp_recaptcha_js_api-js-after </script> <script id="all-js" src="https://sumberlaba.com/wp-content/themes/prime-education/js/all.min.js?ver=6.1.1"></script> <script id="v4-shims-js" src="https://sumberlaba.com/wp-content/themes/prime-education/js/v4-shims.min.js?ver=6.1.1"></script> <script id="prime-education-modal-accessibility-js" src="https://sumberlaba.com/wp-content/themes/prime-education/js/modal-accessibility.min.js?ver=1.0"></script> <script id="owl.carousel-js" src="https://sumberlaba.com/wp-content/themes/prime-education/js/build/owl.carousel.js?ver=2.6.0"></script> <script id="prime-education-js-js" src="https://sumberlaba.com/wp-content/themes/prime-education/js/build/custom.js?ver=1.0"></script> <script async data-wp-strategy="async" fetchpriority="low" id="comment-reply-js" src="https://sumberlaba.com/wp-includes/js/comment-reply.min.js?ver=7.0"></script> <script id="wp-emoji-settings" type="application/json"> {"baseUrl":"https://s.w.org/images/core/emoji/17.0.2/72x72/","ext":".png","svgUrl":"https://s.w.org/images/core/emoji/17.0.2/svg/","svgExt":".svg","source":{"concatemoji":"https://sumberlaba.com/wp-includes/js/wp-emoji-release.min.js?ver=7.0"}} </script> <script type="module"> /*! This file is auto-generated */ const a=JSON.parse(document.getElementById("wp-emoji-settings").textContent),o=(window._wpemojiSettings=a,"wpEmojiSettingsSupports"),s=["flag","emoji"];function i(e){try{var t={supportTests:e,timestamp:(new Date).valueOf()};sessionStorage.setItem(o,JSON.stringify(t))}catch(e){}}function c(e,t,n){e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(t,0,0);t=new Uint32Array(e.getImageData(0,0,e.canvas.width,e.canvas.height).data);e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(n,0,0);const a=new Uint32Array(e.getImageData(0,0,e.canvas.width,e.canvas.height).data);return t.every((e,t)=>e===a[t])}function p(e,t){e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(t,0,0);var n=e.getImageData(16,16,1,1);for(let e=0;e<n.data.length;e++)if(0!==n.data[e])return!1;return!0}function u(e,t,n,a){switch(t){case"flag":return n(e,"\ud83c\udff3\ufe0f\u200d\u26a7\ufe0f","\ud83c\udff3\ufe0f\u200b\u26a7\ufe0f")?!1:!n(e,"\ud83c\udde8\ud83c\uddf6","\ud83c\udde8\u200b\ud83c\uddf6")&&!n(e,"\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f","\ud83c\udff4\u200b\udb40\udc67\u200b\udb40\udc62\u200b\udb40\udc65\u200b\udb40\udc6e\u200b\udb40\udc67\u200b\udb40\udc7f");case"emoji":return!a(e,"\ud83e\u1fac8")}return!1}function f(e,t,n,a){let r;const o=(r="undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?new OffscreenCanvas(300,150):document.createElement("canvas")).getContext("2d",{willReadFrequently:!0}),s=(o.textBaseline="top",o.font="600 32px Arial",{});return e.forEach(e=>{s[e]=t(o,e,n,a)}),s}function r(e){var t=document.createElement("script");t.src=e,t.defer=!0,document.head.appendChild(t)}a.supports={everything:!0,everythingExceptFlag:!0},new Promise(t=>{let n=function(){try{var e=JSON.parse(sessionStorage.getItem(o));if("object"==typeof e&&"number"==typeof e.timestamp&&(new Date).valueOf()<e.timestamp+604800&&"object"==typeof e.supportTests)return e.supportTests}catch(e){}return null}();if(!n){if("undefined"!=typeof Worker&&"undefined"!=typeof OffscreenCanvas&&"undefined"!=typeof URL&&URL.createObjectURL&&"undefined"!=typeof Blob)try{var e="postMessage("+f.toString()+"("+[JSON.stringify(s),u.toString(),c.toString(),p.toString()].join(",")+"));",a=new Blob([e],{type:"text/javascript"});const r=new Worker(URL.createObjectURL(a),{name:"wpTestEmojiSupports"});return void(r.onmessage=e=>{i(n=e.data),r.terminate(),t(n)})}catch(e){}i(n=f(s,u,c,p))}t(n)}).then(e=>{for(const n in e)a.supports[n]=e[n],a.supports.everything=a.supports.everything&&a.supports[n],"flag"!==n&&(a.supports.everythingExceptFlag=a.supports.everythingExceptFlag&&a.supports[n]);var t;a.supports.everythingExceptFlag=a.supports.everythingExceptFlag&&!a.supports.flag,a.supports.everything||((t=a.source||{}).concatemoji?r(t.concatemoji):t.wpemoji&&t.twemoji&&(r(t.twemoji),r(t.wpemoji)))}); //# sourceURL=https://sumberlaba.com/wp-includes/js/wp-emoji-loader.min.js </script> </body> </html>