How to Prototype a Web App with Django and Vue.js
Wouldn’t it be cool if you could prototype a custom web application that’s responsive (mobile-ready), reactive (light-speed fast), with a full-featured admin interface to manage the content — all in no time? Actually, using Django and Vue.js, you can! 😁
0. Introduction: Full Stack Development 101
I’ll cover how to prototype a custom web app, and to keep it as short and sweet as possible, the context information here will be rather brief. However, I will provide — hopefully — enough resources so that you know where to go should you need more information.
To this end I’ll fully mash up data management, presentation, and routing between Django and Vue.js — so get ready!
About Django
You may know of Django, the Python-based web framework for perfectionists with deadlines that’s ridiculously fast, reassuringly secure, and exceedingly scalable. But if you don’t know much about it, this article will serve as an extremely fast hands-on introduction.
About Vue.js
Same deal with Vue.js, the progressive JavaScript framework that’s approachable, versatile, and performant. If you aren’t familiar with it, here you’ll get a quick-and-dirty introduction.
I’ll also cover two official Vue.js libraries:
- Vue Router for route/view mapping
- Vuex for state management
Python + JavaScript = WIN!
For this article, we’ll set a publishing project with a basic database schema to save authors
and articles
, and a minimal user interface (UI) to interact with them.
Hopefully, this will serve as a toy program to understand how to integrate Django using Python code on the back end with a JavaScript framework on the front end, that you can adapt later to your own needs.
1. Setting Up a Django Project
Very quickly, we’ll set up a project from scratch. If you already know how to handle Django projects, you can skip this section. We’ll assume you already have Python installed.
For a more in-depth guide, see How to install Django on the Django documentation site.
Python Virtual Environment
Let’s open a console and create a virtual environment (see Virtual Environments in Python Made Easy for more info):
$ virtualenv myenvironment
Using base prefix 'c:\\users\\luzdealba\\appdata\\local\\programs\\python\\python37'
New python executable in C:\Users\luzdealba\Development\myenvironment\Scripts\python.exe
Installing setuptools, pip, wheel …
done
Don’t mind the paths, as these will change from system to system.
Let’s access and activate the virtual environment.
On Windows:
$ cd myenvironment
$ Scripts\activate
On macOS and Linux:
$ cd myenvironment
$ source bin/activate
Django Package
Let’s install Django:
(myenvironment) $ pip install django
Collecting django
Downloading Django-3.0.3-py3-none-any.whl (7.5 MB)
Collecting sqlparse>=0.2.2
Downloading sqlparse-0.3.1-py2.py3-none-any.whl (40 kB)
Collecting pytz
Using cached pytz-2019.3-py2.py3-none-any.whl (509 kB)
Collecting asgiref~=3.2
Downloading asgiref-3.2.3-py2.py3-none-any.whl (18 kB)
Installing collected packages: sqlparse, pytz, asgiref, django
Successfully installed asgiref-3.2.3 django-3.0.3 pytz-2019.3 sqlparse-0.3.1
One more time, don’t mind about program versions and file sizes as these will vary.
The Project
Let’s start a project called myproject
:
(myenvironment) $ django-admin startproject myproject
Let’s access the project:
(myenvironment) $ cd myproject
The App
Start an app called myapp
:
(myenvironment) $ django-admin startapp myapp
And add myapp.apps.MyappConfig
to the INSTALLED_APPS
constant list in myproject/settings.py
to enable the app.
2. Setting Up the Database with Django
Here we’ll define the back-end database with Django, which we’ll later integrate with a front-end storage with Vuex.
Django Models
Models are Django’s way to implement an object-relational database management system (ORDBMS). In other words, plain text files where you can define database tables and fields, and from where these will propagate to the application layer and the DB engine.
Let’s code the following models for our app in myapp/models.py
:
from django.db import models
class Article(models.Model):
"""Table schema to store articles."""
name = models.CharField(max_length=64)
author = models.ForeignKey('myapp.Author', on_delete=models.CASCADE)
content = models.TextField()
slug = models.CharField(default='', max_length=64)
def __str__(self):
return '%s' % self.name
class Author(models.Model):
"""Table schema to store auhtors."""
name = models.CharField(max_length=64)
slug = models.CharField(default='', max_length=64)
def __str__(self):
return '%s' % self.name
Notice that we implemented a URL slug for both articles and authors.
For more information, see Model API reference on the Django documentation site.
Django Admin
Before we can manage these models through the admin site, we’ll first need to register
them so that Django makes them available to us.
Let’s simply edit myapp/admin.py
so that it looks like this:
from django.contrib import admin
from .models import Article
from .models import Author
# register models to use in admin site
admin.site.register(Article)
admin.site.register(Author)
Read more about the Django admin site on the Django documentation site.
Django Migrations
From the Django’s Migrations documentation:
Migrations are Django’s way of propagating changes you make to your models (adding a field, deleting a model, etc.) into your database schema.
In short: migrations do everything; no SQL commands required.
First, let’s create the migration files:
(myenvironment) $ python manage.py makemigrations
Migrations for 'myapp':
myapp\migrations\0001_initial.py
- Create model Author
- Create model Article
Now let’s use that information to update the database:
(myenvironment) $ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, myapp, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying admin.0003_logentry_add_action_flag_choices... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying auth.0010_alter_group_name_max_length... OK
Applying auth.0011_update_proxy_permissions... OK
Applying myapp.0001_initial... OK
Applying sessions.0001_initial... OK
Don’t mind the long list. That’s because that was our first migration, so not only our Author
and Article
tables were created, but also all of Django’s default schemas.
For more info, see Migration operations in the Django documentation site.
3. A Basic Interface with Vue Components in a Django Template
Here’s one of the most interesting parts of the mashup, as we’ll mix closely related tools from different technologies.
Django View
Django goes by the model–view–controller (MVC) software design pattern, which divides the related program logic into three interconnected elements.
We’ll code the following view in myapp/views.py
:
from django.shortcuts import render
from .models import Article
from .models import Author
def frontend(request):
"""Vue.js will take care of everything else."""
articles = Article.objects.all()
authors = Author.objects.all()
data = {
'articles': articles,
'authors': authors,
}
return render(request, 'myapp/template.html', data)
Notice that we queried all of the articles and authors form the database. That will come in handy later.
See more about writing views and class-based views (API) in the Django documentation site.
Django Template
Django has a rich template language with built-in template tags and filters, and an API for Python programmers; but yes, you guessed it — we won’t cover much of that here. 😅
What we will do, however, is use Bootstrap’s Starter template to set a very basic navigation layout for the app:
- light gray background
- white foreground
- centered content
So we’ll code the following template in myapp/templates/myapp/template.html
(you’ll need to create the sub-directories templates/myapp/
within myapp/
):
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<title>Django and Vue.js</title>
</head>
<body class="bg-light">
<div class="bg-white container">
<h1>Prototyping a Web App with Django and Vue.js</h1>
<!-- Content -->
</div>
<!-- Vue.js -->
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vue-router"></script>
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
</body>
</html>
Read more about Django templates.
Vue.js Templates
Vue.js also has a template syntax available, and it furthermore allows us to create our own HTML tags.
We’ll create four templates: article-list
, author-list
, article-item
and author-item
:
<template id="article-list-template">
<div class="article-list">
<h2>Articles</h2>
<article-item
v-for="article in articles"
v-bind:key="article.slug"
v-bind:name="article.name"
v-bind:slug="article.slug"
v-bind:content="article.content"
></article-item>
</div>
</template>
<template id="author-list-template">
<div class="author-list">
<h2>Authors</h2>
<author-item
v-for="author in authors"
v-bind:key="author.slug"
v-bind:name="author.name"
v-bind:slug="author.slug"
></author-item>
</div>
</template>
<template id="article-item-template">
<div class="article-item">
<span v-if="$route.params.slug">
<h3>
<router-link
v-bind:to="'/article/' + $route.params.slug + '/'"
v-html="$store.getters.getArticleBySlug($route.params.slug)['name']"
></router-link>
</h3>
<div v-html="$store.getters.getArticleBySlug($route.params.slug)['content']"></div>
</span>
<span v-else>
<h3>
<router-link
v-bind:to="'/article/' + slug + '/'"
v-html="name"
></router-link>
</h3>
<div v-html="content"></div>
<hr />
</span>
</div>
</template>
<template id="author-item-template">
<div class="author-item">
<span v-if="$route.params.slug">
<b>
<router-link v-bind:to="'/author/' + $route.params.slug + '/'">
[[ $store.getters.getAuthorBySlug($route.params.slug)['name'] ]]
</router-link>
</b>
([[ $route.params.slug ]])
</span>
<span v-else>
<b>
<router-link v-bind:to="'/author/' + slug + '/'">
[[ name ]]
</router-link>
</b> ([[ slug ]])
</span>
</div>
</template>
Breakdown
What we did here, in a nutshell:
- List rendering with
v-for
. - Data binding in HTML attributes with
v-bind
. - Conditional rendering with
v-if
andv-else
. - Raw HTML rendering with
v-html
.
$store
and $route
will make more sense in a moment when we introduce storage and routing.
Text Interpolation between Django and Vue.js Templates
Just like with Django, the most basic form of text interpolation in Vue.js is with the “Mustache” syntax (double curly brackets).
Like this:
<span>Message: {{ msg }}</span>
In order to avoid conflicts between the two, when instantiating Vue.js we’ll set it to use double square brackets instead:
delimiters: ['[[', ']]']
Vue.js Themes
Did you know you can “theme” Vue.js?
- Drop-in themes for Vue.js
- Vuetify.js, Material Design component framework
- BootstrapVue, Bootstrap components and grid system for Vue.js
- Buefy, lightweight UI components for Vue.js based on Bulma
- Element UI, a component library for developers, designers and product managers
- Vue Material, Material Design and Vue.js
- Quasar Framework, high-performance Vue.js UI
Vue.js Components
Components are reusable Vue instances. What that means is that we can define a component, code an HTML template for it, and then use it as many times as we need it with Vue.js handling the DOM for us.
We’ll show the entire single-page application (SPA) code in the end. For now, let’s introduce a few snippets.
Just as with templates, we’ll define four components — ArticleList
, AuthorList,
, ArticleItem
, and AuthorItem
:
ArticleList = Vue.component('article-list', {
data: function () { return { articles: store.state.articles } },
template: '#article-list-template',
});
AuthorList = Vue.component('author-list', {
data: function () { return { authors: store.state.authors } },
template: '#author-list-template',
});
ArticleItem = Vue.component('article-item', {
delimiters: ['[[', ']]'],
props: ['name', 'slug', 'content'],
template: '#article-item-template',
});
AuthorItem = Vue.component('author-item', {
delimiters: ['[[', ']]'],
props: ['name', 'slug'],
template: '#author-item-template',
});
Breakdown
- When in a component,
data
must be a function ($store
will be explained in a moment). - We use the templates previously defined.
- To disambiguate text interpolation, make sure you set
delimiters
that are different from Django’s ({{
/}}
). - We use
props
listed as an array to pass data to our components.
4. Connecting Vue.js Store to Collect Django’s Database Using Vuex
A rich front end might have many loosely coupled components, each with its own set of parameters, which can make it complicated to share data or to manage the state of variables.
Here’s where Vuex comes in handy:
It serves as a centralized store for all the components in an application, with rules ensuring that the state can only be mutated in a predictable fashion.
Creating a Vuex Store
Let’s create and define a store in our Django template, and let’s use the data shared in the Django View:
const store = new Vuex.Store({
state: {
authors: [
{% for author in authors %}
{
name: '{{ author.name }}',
slug: '{{ author.slug }}',
},
{% endfor %}
],
articles: [
{% for article in articles %}
{
content: '{{ article.content | linebreaksbr }}',
name: '{{ article.name }}',
slug: '{{ article.slug }}',
},
{% endfor %}
],
},
getters: {
getArticleBySlug: (state) => (slug) => {
return state.articles.find(articles => articles.slug === slug)
},
getAuthorBySlug: (state) => (slug) => {
return state.authors.find(authors => authors.slug === slug)
},
}
})
Breakdown
Let’s review what just happened:
- We’ve created a store using
Vuex.Store()
. - We defined a Vuex state where all of the
articles
andauthors
are collected. - We used the
for
loop that’s built into the Django templates to iterate through all of thearticles
andauthors
. - We’ve created two Vuex getters to get an article or an author by their slug,
getArticleBySlug
andgetAuthorBySlug
, respectively.
There’s a lot more to Vuex, so make sure to check the Getting Started guide and the API reference.
5. Routing URLs between Django and Vue Router
Django has a powerful URL dispatcher, which we’ll use in combination with Vue.js routing.
We’ll create an application that:
- uses dynamic route matching to seamlessly switch between pages without refreshes (see example)
- works with nested routes (see example)
With Django
Reactive URLs usually need a special configuration on the server to work properly, but Django lets us design URLs however we want, so no need to set rewrite rules for Apache or NGINX.
We’ll edit myproject/urls.py
so that it uses the base directory /
as the route for our app:
from django.contrib import admin
from django.urls import path
# don't forget to import the app's view!
from myapp import views as myapp_views
urlpatterns = [
path('admin/', admin.site.urls),
# paths for our app
path('', myapp_views.frontend),
path('article/<slug:slug>/', myapp_views.frontend),
path('author/<slug:slug>/', myapp_views.frontend),
]
With Vue Router
By default, Vue Router uses “hash mode” (i.e: http://site/#/path) as a JavaScript trick to load parts of the page using anchors. However, we’ll leverage Vue Router’s HTML5 History Mode, which means that all of our URLs will change seamlessly without reloading the page and without using hashes.
We’ll set the router to match each path to their respective component previously defined:
const routes = [
{ component: ArticleList, path: '/article/', },
{ component: AuthorList, path: '/author/', },
{ component: ArticleItem, path: '/article/:slug/', },
{ component: AuthorItem, path: '/author/:slug/', },
]
const router = new VueRouter({
mode: 'history',
routes: routes,
})
As we can see, the syntax to define paths is slightly different from Django’s, but it’s essentially the same thing.
Read more about the Vue Router.
6. Testing Everything
Now that we’ve got all of the pieces together, it’s time to do some gray box testing and see how things work!
Create a Django Superuser
Before we can log in to the admin, we’ll need to create a superuser.
Let’s create an administrator:
(myenvironment) $ python manage.py createsuperuser
Next, you’ll enter the username, email address, and password (twice).
Run a Local Server
We’ll run Django’s built-in server with runserver
to launch the website on our local system.
On a console:
(myenvironment) $ python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
March 09, 2020 - 19:41:22
Django version 3.0.3, using settings 'myproject.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
Create Database Entries
We’ll now populate the database so that we can browse something on the front end.
Let’s head to http://127.0.0.1:8000/admin/ and enter the admin credentials you’ve just created so that we can create two authors and four articles:
- On the MYAPP pane, next to Authors, click on the Add link and create at least two authors.
- On the MYAPP pane, next to Articles, click on the Add link and create at least two different articles for each author.
Notice that you must add articles after having created a few authors so that you can link them.
Browse the Site!
Now’s the time to see how it all plays together!
Full SPA Code
You can navigate all of the project code in my GitHub repository, luzdealba / djangovuejs.
Anyway, this is probably what you’re most interested in:
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<title>Django and Vue.js</title>
<style>
.router-link-active {
color: black;
text-decoration: none;
}
</style>
</head>
<body class="bg-light">
<div class="bg-white container">
<div class="jumbotron">
<h1 class="display-4">Django and Vue.js</h1>
<p class="lead">
Wouldn’t it be cool if you could prototype a custom web application that’s responsive (mobile ready), reactive (light-speed fast), with a full–featured back office site to manage the content; all of that in no time? Actually, with a mashup between Django’s and Vue.js, you can! 😁
</p>
</div>
<!-- Content -->
<div id="myapp">
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<router-link
class="nav-link text-primary"
to="/author/"
>
Go to Authors
</router-link>
</li>
<li class="nav-item">
<router-link
class="nav-link text-primary"
to="/article/"
>
Go to Articles
</router-link>
</li>
</ul>
</nav>
<br />
<router-view></router-view>
</div>
</div>
<!-- Vue.js -->
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vue-router"></script>
<script src="https://unpkg.com/vuex"></script>
<!-- Vue templates -->
<template id="article-list-template">
<div class="article-list">
<h2>Articles</h2>
<article-item
v-for="article in articles"
v-bind:key="article.slug"
v-bind:name="article.name"
v-bind:slug="article.slug"
v-bind:content="article.content"
></article-item>
</div>
</template>
<template id="author-list-template">
<div class="author-list">
<h2>Authors</h2>
<author-item
v-for="author in authors"
v-bind:key="author.slug"
v-bind:name="author.name"
v-bind:slug="author.slug"
></author-item>
</div>
</template>
<template id="article-item-template">
<div class="article-item">
<span v-if="$route.params.slug">
<h3>
<router-link
v-bind:to="'/article/' + $route.params.slug + '/'"
v-html="$store.getters.getArticleBySlug($route.params.slug)['name']"
></router-link>
</h3>
<div v-html="$store.getters.getArticleBySlug($route.params.slug)['content']"></div>
</span>
<span v-else>
<h3>
<router-link
v-bind:to="'/article/' + slug + '/'"
v-html="name"
></router-link>
</h3>
<div v-html="content"></div>
<hr />
</span>
</div>
</template>
<template id="author-item-template">
<div class="author-item">
<span v-if="$route.params.slug">
<b>
<router-link v-bind:to="'/author/' + $route.params.slug + '/'">
[[ $store.getters.getAuthorBySlug($route.params.slug)['name'] ]]
</router-link>
</b>
([[ $route.params.slug ]])
</span>
<span v-else>
<b>
<router-link v-bind:to="'/author/' + slug + '/'">
[[ name ]]
</router-link>
</b> ([[ slug ]])
</span>
</div>
</template>
<!-- Vue app -->
<script>
// store
const store = new Vuex.Store({
state: {
authors: [
{% for author in authors %}
{
name: '{{ author.name }}',
slug: '{{ author.slug }}',
},
{% endfor %}
],
articles: [
{% for article in articles %}
{
content: '{{ article.content | linebreaksbr }}',
name: '{{ article.name }}',
slug: '{{ article.slug }}',
},
{% endfor %}
],
},
getters: {
getArticleBySlug: (state) => (slug) => {
return state.articles.find(articles => articles.slug === slug)
},
getAuthorBySlug: (state) => (slug) => {
return state.authors.find(authors => authors.slug === slug)
},
}
})
// components
ArticleList = Vue.component('article-list', {
data: function () { return { articles: store.state.articles } },
template: '#article-list-template',
});
AuthorList = Vue.component('author-list', {
data: function () { return { authors: store.state.authors } },
template: '#author-list-template',
});
ArticleItem = Vue.component('article-item', {
delimiters: ['[[', ']]'],
props: ['name', 'slug', 'content'],
template: '#article-item-template',
});
AuthorItem = Vue.component('author-item', {
delimiters: ['[[', ']]'],
props: ['name', 'slug'],
template: '#author-item-template',
});
// router
const routes = [
{ component: ArticleList, path: '/article/', },
{ component: AuthorList, path: '/author/', },
{ component: ArticleItem, path: '/article/:slug/', },
{ component: AuthorItem, path: '/author/:slug/', },
]
const router = new VueRouter({
mode: 'history',
routes: routes,
})
// app
const myapp = new Vue({
router,
store,
}).$mount('#myapp');
</script>
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
</body>
</html>
Looking Forward: Microservices!
At this point, you already have a solid prototype that can work as a proof of concept to present an idea to your prospect clients or colleagues, or as a foundation for your own project.
While the interface we created can present database registries, you can’t really interact with them in any other way through the front end, such as making new additions, editions, or deleting such entries. For that, you’ll need an API.
Believe it or not, implementing a microservice through a REST API is fairly straightforward with Django. All you need is the Django REST framework add-on, which is extremely well documented and, as all things Django, powerful, flexible, and secure.
With an exposed API, the next thing you can do is manage data right there on your front end with Vue.js. I can’t cover the details here, but you can check the “Using Axios to Consume APIs” article in the Vue.js Cookbook.
Wrapping Up
How’s that for a primer on full-stack development? We’ve prototyped a project that can be the foundation for a web application.
And I didn’t water down anything! In fact, because we’re using Vuex storage for state management and Vue Router for dynamic route matching from the get-go, there aren’t substantial changes that we’ll need to do as the application scales. So you can essentially take it from there and expand in whichever direction you need to — customizing the database, improving the interface, and even creating a microservice!
Don’t be shy if your Python or JavaScript knowledge is somewhat limited. We all need to start somewhere. Read further, code further, and stay curious!