{"id":21454,"date":"2017-01-04T13:44:42","date_gmt":"2017-01-04T13:44:42","guid":{"rendered":"http:\/\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2"},"modified":"2017-01-04T13:44:42","modified_gmt":"2017-01-04T13:44:42","slug":"how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2","title":{"rendered":"How to build a CMS with Adonis: A Laravel-like MVC framework for Node &#8211; Part 2"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p>Even though Node is fun, easy and cheap to work with, we spend a lot of time writing boilerplate codes because structure and organization is missing.<\/p>\n<p>In <a href=\"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_1\">part 1<\/a>, we discussed the basics of Adonis, including how to setup Adonis projects, and create migrations, models, a few routes, and a controller to test the creation of new posts.<\/p>\n<p>Now, let\u2019s extend what we already know to reading posts, updating existing posts, deleting posts, and adding an image upload feature.<\/p>\n<h2>Reading existing posts<\/h2>\n<p>With the create feature implemented, we must have created posts in our store. These posts will be read from our store and displayed on the browser.<\/p>\n<p>We need to update the index route to point to a controller, rather than sending a view directly as it already does:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ .\/app\/Http\/routes.js<\/span>\n...\n<span class=\"hljs-comment\">\/\/ What we had:<\/span>\n<span class=\"hljs-comment\">\/\/ Route.on('\/').render('welcome');<\/span>\n<span class=\"hljs-comment\">\/\/ Update to:<\/span>\nRoute.get(<span class=\"hljs-string\">'\/'<\/span>, <span class=\"hljs-string\">'PostController.index'<\/span>);\n...\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Next, we add an action method to our controller named <code>index<\/code>. This method will be responsible for fetching the data from our model and sending the data to our view:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ .\/app\/Http\/Controllers\/PostController.js<\/span>\n...\n* index(request, response) {\n    <span class=\"hljs-keyword\">const<\/span> posts = <span class=\"hljs-keyword\">yield<\/span> Post.all();\n    <span class=\"hljs-keyword\">yield<\/span> response.sendView(<span class=\"hljs-string\">'post\/index'<\/span>, {<span class=\"hljs-attr\">posts<\/span>: posts.toJSON()});\n}\n...\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>The route points to an existing method, which sends a view with our retrieved data. Let\u2019s create this view:<\/p>\n<pre class=\"js-syntax-highlighted\"><code>&lt;h2&gt;Articles &lt;a href=&quot;\/new&quot; class=&quot;ui blue button&quot;&gt;New Post&lt;\/a&gt;&lt;\/h2&gt;\n&lt;div class=&quot;ards&quot;&gt;\n  {% for post in posts %}\n  &lt;div class=&quot;card&quot;&gt;\n    &lt;div class=&quot;content&quot;&gt;\n        &lt;a class=&quot;header&quot;&gt;{{post.title}}&lt;\/a&gt;\n        &lt;div class=&quot;meta&quot;&gt;\n            &lt;span class=&quot;date&quot;&gt;{{post.created_at}}&lt;\/span&gt;\n        &lt;\/div&gt;\n        &lt;div class=&quot;description&quot;&gt;\n        {{post.body.substring(0, 50)}}...\n        &lt;\/div&gt;\n    &lt;\/div&gt;\n  &lt;\/div&gt;\n  % else %}\n  &lt;h3&gt; No posts found &lt;\/h3&gt;\n  {% endfor %}\n&lt;\/div&gt;\n<\/code><\/pre>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/cloudinary\/image\/upload\/w_700\/adonis_post.png\" alt=\"Adonis Post\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"700\" height=\"437\"\/><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/cloudinary\/image\/upload\/w_700\/adonis_post_titles.png\" alt=\"Adonis Post Title\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"700\" height=\"430\"\/><\/p>\n<h3>Reading a single post<\/h3>\n<p>One other form of showing posts is reading and displaying a single post, which gives us more room for the details about the post. To do this, you need a route and a controller. Here is the route:<\/p>\n<pre class=\"js-syntax-highlighted\"><code>\/\/ .\/app\/Http\/routes.js\nRoute.get('\/post\/:id', 'PostController.read');\n<\/code><\/pre>\n<p>The <code>:id<\/code> part of the URL is a placeholder for the route parameter. The parameter will be a unique value that points to a given post.<\/p>\n<pre class=\"js-syntax-highlighted\"><code>\/\/ .\/app\/Http\/Controllers\/PostController.js\n* read(request, response) {\n     \/\/ Receive parameter from request\n     const id = request.param('id');\n     \/\/ Find id with request parameter\n     const post = yield Post.find(id);\n     yield response.sendView('post\/read', {post: post});\n }\n<\/code><\/pre>\n<p>The controller\u2019s read action method receives the <code>id<\/code> parameter from the <code>request<\/code> object. We then use the model\u2019s <code>find<\/code> method to find a post based on the value passed in.<\/p>\n<p>The view sent is named <code>read<\/code> in the post folder and we are passing the post data down to the view as well:<\/p>\n<pre class=\"js-syntax-highlighted\"><code>&lt;h2&gt;{{post.title}}&lt;\/h2&gt;\n&lt;small&gt;{{post.created_at}}&lt;\/small&gt;\n&lt;p&gt;{{post.body}}&lt;\/p&gt;\n<\/code><\/pre>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/cloudinary\/image\/upload\/w_700\/adonis_post_second_title.png\" alt=\"Adonis Post Second Title\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"700\" height=\"573\"\/><\/p>\n<p>You can update the link for each post on the home page to point to the read URL:<\/p>\n<pre class=\"js-syntax-highlighted\"><code>&lt;a class=&quot;header&quot; href=&quot;\/post\/{{post.id}}&quot;&gt;{{post.title}}&lt;\/a&gt;\n<\/code><\/pre>\n<h2>Updating posts<\/h2>\n<p>What happens when we realize that our post requires an update? We can create a form just like we did for the new post form, but this time we will send the existing post to the form. First, we need to specify some routes, as usual:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ .\/app\/Http\/routes.js<\/span>\n<span class=\"hljs-comment\">\/\/ Edit form<\/span>\nRoute.get(<span class=\"hljs-string\">'\/edit\/:id'<\/span>, <span class=\"hljs-string\">'PostController.edit'<\/span>);\nRoute.post(<span class=\"hljs-string\">'\/update'<\/span>, <span class=\"hljs-string\">'PostController.update'<\/span>);\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>With respect to the routes above, the following action methods will serve as handlers for both routes:<\/p>\n<pre class=\"js-syntax-highlighted\"><code>* edit(request, response) {\n    const id = request.param('id');\n    const post = yield Post.find(id);\n    yield response.sendView('post\/edit', {post: post});\n}\n* update(request, response) {\n    var postData = request.only('id', 'title', 'body');\n    const id = postData.id;\n    const post = yield Post.find(id);\n    \/\/ Update and save post\n    post.fill(postData);\n    yield post.save();\n    \/\/ Go home\n    response.redirect('\/');\n}\n<\/code><\/pre>\n<p>The <code>edit<\/code> action method sends a view. This view will hold our form template for editing the selected post:<\/p>\n<pre class=\"js-syntax-highlighted\"><code>  &lt;h2&gt;{{post.title}}&lt;\/h2&gt;\n  {{ form.open({action: 'PostController.update'}) }}\n\n    {{ csrfField }}\n\n    &lt;div class=&quot;ui form&quot;&gt;\n        &lt;div class=&quot;field&quot;&gt;\n            {{ form.label('Title') }}\n            {{ form.text('title', post.title) }}\n        &lt;\/div&gt;\n\n        &lt;div class=&quot;field&quot;&gt;\n            {{ form.label('Body') }}\n            {{ form.textarea('body', post.body) }}\n        &lt;\/div&gt;\n\n        {{form.hidden('id', post.id)}}\n\n        {{ form.submit('Create', 'create', { class: 'ui blue button' }) }}\n    &lt;\/div&gt;\n\n  {{ form.close() }}\n<\/code><\/pre>\n<p>The default values are set using the existing values in our store. The post\u2019s <code>id<\/code> is also available on the form via a hidden input.<\/p>\n<p>For each post listed on the home page, we\u2019ll also add an edit button that points to the relevant edit URL:<\/p>\n<pre class=\"js-syntax-highlighted\"><code>...\n&lt;a class=&quot;ui basic green button&quot; href=&quot;\/edit\/{{post.id}}&quot;&gt;Edit&lt;\/a&gt;\n...\n<\/code><\/pre>\n<h2>Deleting posts<\/h2>\n<p>The route for deleting post is very similar to the <code>read<\/code> and <code>edit<\/code> routes. It takes a parameter for searching the post to be removed:<\/p>\n<pre class=\"js-syntax-highlighted\"><code>Route.get('\/delete\/:id', 'PostController.delete');\n<\/code><\/pre>\n<p><strong>Note<\/strong>: Best practices suggest that you do not use <code>GET<\/code> to update state as we are doing right now. For the sake of this demo\u2019s simplicity, we can overlook that practice.<\/p>\n<p>The action method on the controller is <code>delete<\/code> and it finds the post based on the <code>id<\/code> parameter and then deletes the found post:<\/p>\n<pre class=\"js-syntax-highlighted\"><code>* delete(request, response) {\n    var post = yield Post.find(request.param('id'));\n    yield post.delete();\n    response.redirect('\/');\n}\n<\/code><\/pre>\n<p>We can add a link to the post list to point to the delete URL:<\/p>\n<pre class=\"js-syntax-highlighted\"><code>&lt;a class=&quot;ui basic red button&quot; href=&quot;\/delete\/{{post.id}}&quot;&gt;Delete&lt;\/a&gt;\n<\/code><\/pre>\n<h2>Featured image uploads<\/h2>\n<p>Contents are always accompanied with images. In the case of a CMS, these images could be embedded right inside the content or serve as a banner to a given content. The banner is usually known as a featured image.<\/p>\n<p><a href=\"https:\/\/cloudinary.com\/\">Cloudinary<\/a> is the image management back-end for web and mobile developers. With Cloudinary, we can add images to our content with ease, thereby saving as the hassle of managing storage, as well as image uploads, downloads, manipulation, delivery and administration.<\/p>\n<p>With the Cloudinary <a href=\"https:\/\/cloudinary.com\/users\/register\/free\">free account<\/a>, we can begin using these features in our projects. Let\u2019s see how by adding a featured image to our posts.<\/p>\n<p>After you set up a Cloudinary account, you can create an <a href=\"https:\/\/cloudinary.com\/documentation\/upload_presets\">upload preset<\/a>. Upload presets enable you to centrally define a set of image upload options instead of specifying them in each upload call. We will use the preset when making an upload request.<\/p>\n<p>Cloudinary provides JavaScript plugins that make image upload to the Cloudinary server very easy. Here\u2019s how we include these scripts:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">script<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">'https:\/\/cdn.jsdelivr.net\/jquery.cloudinary\/1.0.18\/jquery.cloudinary.js'<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">'text\/javascript'<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">script<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">script<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"\/\/widget.cloudinary.com\/global\/all.js\"<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"text\/javascript\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">script<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">script<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"\/script.js\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">script<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>The plugin depends on jQuery, so we also added our defined <code>script.js<\/code> file to the setup.<\/p>\n<p>In the <code>script.js<\/code> file, we can start implementing the upload logic:<\/p>\n<pre class=\"js-syntax-highlighted\"><code>$(function() {\n    \/\/ Configure Cloudinary\n    \/\/ with credentials available on\n    \/\/ your Cloudinary account dashboard\n    $.cloudinary.config({ cloud_name: 'YOUR_CLOUD_NAME', api_key: 'YOUR_API_KEY'});\n\n    \/\/ Upload button\n    var uploadButton = $('#upload-button');\n    var canvas = $('#canvas');\n    var imageInput = $('#image-input');\n    \/\/ Upload button event\n    uploadButton.on('click', function(e){\n        \/\/ Initiate upload\n        cloudinary.openUploadWidget({ cloud_name: 'christekh', upload_preset: 'idcidr0h', tags: ['cgal']}, \n        function(error, result) { \n            if(error) console.log(error);\n            \/\/ If NO error, log image data to console\n            var id = result[0].public_id;\n            canvas.html(procesImage(id));\n            imageInput.val($.cloudinary.url(id, {}));\n        });\n    });\n})\n\nfunction procesImage(id) {\n    var options = {\n        client_hints: true,\n    };\n    return '&lt;img src=&quot;'+ $.cloudinary.url(id, options) +'&quot; style=&quot;width: 100%; height: auto&quot;\/&gt;';\n}\n<\/code><\/pre>\n<p>The above listens to a <code>click<\/code> event on a button in our view. When the button is clicked, the upload process starts. After an image is uploaded and returned, the image is displayed above the form. The URL is also embedded in a <code>hidden<\/code> input so it can be sent to the server and stored for future use, as well.<\/p>\n<p>Going back to the <code>posts\/new<\/code> view we already created, we can extend it to handle the logic we have prepared:<\/p>\n<pre class=\"js-syntax-highlighted\"><code>&lt;h2&gt;New Post&lt;\/h2&gt;\n    &lt;div class=&quot;paint_container&quot; id=&quot;canvas&quot;&gt;\n        &lt;!-- Canvas to drop image after processing --&gt;\n    &lt;\/div&gt;\n  {{ form.open({action: 'PostController.create'}) }}\n\n    {{ csrfField }}\n\n    &lt;div class=&quot;ui form&quot;&gt;\n        &lt;div class=&quot;field&quot;&gt;\n            {{ form.label('Title') }}\n            {{ form.text('title', null) }}\n        &lt;\/div&gt;\n\n        &lt;div class=&quot;field&quot;&gt;\n            {{ form.label('Body') }}\n            {{ form.textarea('body', null) }}\n        &lt;\/div&gt;\n\n        &lt;input type=&quot;hidden&quot; id=&quot;image-input&quot; name=&quot;image&quot;&gt;\n\n        &lt;button id=&quot;upload-button&quot; class=&quot;ui purple button&quot; type=&quot;button&quot;&gt;Upload Featured Image&lt;\/button&gt;\n\n        {{ form.submit('Create', 'create', { class: 'ui blue button' }) }}\n    &lt;\/div&gt;\n\n  {{ form.close() }}\n<\/code><\/pre>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/cloudinary\/image\/upload\/w_700\/adonis_new_post.png\" alt=\"Adonis New Post\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"700\" height=\"512\"\/><\/p>\n<p>The image also gets stored in our database, so we can also display it on the home page where we list the posts:<\/p>\n<pre class=\"js-syntax-highlighted\"><code>...\n&lt;div class=&quot;card&quot;&gt;\n    &lt;div class=&quot;image&quot;&gt;\n        &lt;img src=&quot;{{post.image}}&quot; syyle=&quot;width: 100%; heigth: auto;&quot;&gt;\n    &lt;\/div&gt;\n   ...\n<\/code><\/pre>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/cloudinary\/image\/upload\/w_700\/adonis_finished_post.png\" alt=\"Adonis Finished Post\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"700\" height=\"512\"\/><\/p>\n<p>Have a look at the <a href=\"https:\/\/github.com\/christiannwamba\/adonis-cloudinary-cms\">complete demo project<\/a>.<\/p>\n<h2>Conclusion<\/h2>\n<p>Adonis is an awesome framework that is great to work with because of its simplicity. Media management can cause you sleepless nights, not just with Adonis, but any server-side framework. Cloudinary eliminates the stress and shortens the time you spend on media management, enabling you to focus on building out the other aspects of your application.<\/p>\n<table>\n<tr>\n<td style = \"padding: 5px;\">\n<img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/cloudinary\/image\/upload\/c_thumb,w_100\/christian_nwamba.jpg\" alt=\"Christian Nwamba\" title=\"Christian Nwamba\"><\/img><\/td>\n<td style = \"padding: 10px;\"><i><a href=\"https:\/\/twitter.com\/codebeast\" target=\"_new\">Christian Nwamba<\/a> is a code beast, with a passion for instructing computers and understanding it&#8217;s language. In his next life, Chris hopes to remain a computer programmer.<\/i><\/td>\n<\/tr>\n<\/table>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":41,"featured_media":21455,"comment_status":"","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[124,177,214],"class_list":["post-21454","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-frameworks","tag-javascript","tag-node"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Building an Adonis-based CMS including image management<\/title>\n<meta name=\"description\" content=\"This part 2 blog post continues to show how to create a simple CMS using the AdonisJs framework, including implementation of image upload using Cloudinary.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"How to build a CMS with Adonis: A Laravel-like MVC framework for Node - Part 2\" \/>\n<meta property=\"og:description\" content=\"This part 2 blog post continues to show how to create a simple CMS using the AdonisJs framework, including implementation of image upload using Cloudinary.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2017-01-04T13:44:42+00:00\" \/>\n<meta property=\"og:image\" content=\"http:\/\/cloudinary.com\/blog\/wp-content\/uploads\/sites\/12\/2022\/02\/adonis_blog_post-1.png\" \/>\n\t<meta property=\"og:image:width\" content=\"700\" \/>\n\t<meta property=\"og:image:height\" content=\"330\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2\"},\"author\":{\"name\":\"\",\"@id\":\"\"},\"headline\":\"How to build a CMS with Adonis: A Laravel-like MVC framework for Node &#8211; Part 2\",\"datePublished\":\"2017-01-04T13:44:42+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2\"},\"wordCount\":309,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649724569\/Web_Assets\/blog\/adonis_blog_post-1\/adonis_blog_post-1.png?_i=AA\",\"keywords\":[\"Frameworks\",\"Javascript\",\"Node\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2017\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2\",\"url\":\"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2\",\"name\":\"Building an Adonis-based CMS including image management\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649724569\/Web_Assets\/blog\/adonis_blog_post-1\/adonis_blog_post-1.png?_i=AA\",\"datePublished\":\"2017-01-04T13:44:42+00:00\",\"description\":\"This part 2 blog post continues to show how to create a simple CMS using the AdonisJs framework, including implementation of image upload using Cloudinary.\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649724569\/Web_Assets\/blog\/adonis_blog_post-1\/adonis_blog_post-1.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649724569\/Web_Assets\/blog\/adonis_blog_post-1\/adonis_blog_post-1.png?_i=AA\",\"width\":700,\"height\":330},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"How to build a CMS with Adonis: A Laravel-like MVC framework for Node &#8211; Part 2\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"name\":\"Cloudinary Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cloudinary.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\",\"name\":\"Cloudinary Blog\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"width\":312,\"height\":60,\"caption\":\"Cloudinary Blog\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Building an Adonis-based CMS including image management","description":"This part 2 blog post continues to show how to create a simple CMS using the AdonisJs framework, including implementation of image upload using Cloudinary.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2","og_locale":"en_US","og_type":"article","og_title":"How to build a CMS with Adonis: A Laravel-like MVC framework for Node - Part 2","og_description":"This part 2 blog post continues to show how to create a simple CMS using the AdonisJs framework, including implementation of image upload using Cloudinary.","og_url":"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2","og_site_name":"Cloudinary Blog","article_published_time":"2017-01-04T13:44:42+00:00","og_image":[{"width":700,"height":330,"url":"http:\/\/cloudinary.com\/blog\/wp-content\/uploads\/sites\/12\/2022\/02\/adonis_blog_post-1.png","type":"image\/png"}],"twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2"},"author":{"name":"","@id":""},"headline":"How to build a CMS with Adonis: A Laravel-like MVC framework for Node &#8211; Part 2","datePublished":"2017-01-04T13:44:42+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2"},"wordCount":309,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649724569\/Web_Assets\/blog\/adonis_blog_post-1\/adonis_blog_post-1.png?_i=AA","keywords":["Frameworks","Javascript","Node"],"inLanguage":"en-US","copyrightYear":"2017","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2","url":"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2","name":"Building an Adonis-based CMS including image management","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649724569\/Web_Assets\/blog\/adonis_blog_post-1\/adonis_blog_post-1.png?_i=AA","datePublished":"2017-01-04T13:44:42+00:00","description":"This part 2 blog post continues to show how to create a simple CMS using the AdonisJs framework, including implementation of image upload using Cloudinary.","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649724569\/Web_Assets\/blog\/adonis_blog_post-1\/adonis_blog_post-1.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649724569\/Web_Assets\/blog\/adonis_blog_post-1\/adonis_blog_post-1.png?_i=AA","width":700,"height":330},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"How to build a CMS with Adonis: A Laravel-like MVC framework for Node &#8211; Part 2"}]},{"@type":"WebSite","@id":"https:\/\/cloudinary.com\/blog\/#website","url":"https:\/\/cloudinary.com\/blog\/","name":"Cloudinary Blog","description":"","publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cloudinary.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cloudinary.com\/blog\/#organization","name":"Cloudinary Blog","url":"https:\/\/cloudinary.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","width":312,"height":60,"caption":"Cloudinary Blog"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":""}]}},"parsely":{"version":"1.1.0","canonical_url":"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2","smart_links":{"inbound":0,"outbound":0},"traffic_boost_suggestions_count":0,"meta":{"@context":"https:\/\/schema.org","@type":"NewsArticle","headline":"How to build a CMS with Adonis: A Laravel-like MVC framework for Node &#8211; Part 2","url":"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2","mainEntityOfPage":{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649724569\/Web_Assets\/blog\/adonis_blog_post-1\/adonis_blog_post-1.png?_i=AA&w=150&h=150&crop=1","image":{"@type":"ImageObject","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649724569\/Web_Assets\/blog\/adonis_blog_post-1\/adonis_blog_post-1.png?_i=AA"},"articleSection":"Uncategorized","author":[],"creator":[],"publisher":{"@type":"Organization","name":"Cloudinary Blog","logo":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA"},"keywords":["frameworks","javascript","node"],"dateCreated":"2017-01-04T13:44:42Z","datePublished":"2017-01-04T13:44:42Z","dateModified":"2017-01-04T13:44:42Z"},"rendered":"<meta name=\"parsely-title\" content=\"How to build a CMS with Adonis: A Laravel-like MVC framework for Node &#8211; Part 2\" \/>\n<meta name=\"parsely-link\" content=\"https:\/\/cloudinary.com\/blog\/how_to_build_a_cms_with_adonis_a_laravel_like_mvc_framework_for_node_part_2\" \/>\n<meta name=\"parsely-type\" content=\"post\" \/>\n<meta name=\"parsely-image-url\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649724569\/Web_Assets\/blog\/adonis_blog_post-1\/adonis_blog_post-1.png?_i=AA&w=150&amp;h=150&amp;crop=1\" \/>\n<meta name=\"parsely-pub-date\" content=\"2017-01-04T13:44:42Z\" \/>\n<meta name=\"parsely-section\" content=\"Uncategorized\" \/>\n<meta name=\"parsely-tags\" content=\"frameworks,javascript,node\" \/>","tracker_url":"https:\/\/cdn.parsely.com\/keys\/cloudinary.com\/p.js"},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649724569\/Web_Assets\/blog\/adonis_blog_post-1\/adonis_blog_post-1.png?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/21454","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/users\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=21454"}],"version-history":[{"count":0,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/21454\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/21455"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=21454"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=21454"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=21454"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}