Basic LinkedList implementation
Notes

Currently I'm aware that each node is created on the Heap.

No such thing as heap in C++ (or stack technically).
You mean dynamically allocated memory in the situation.
Note: Though the language does not specify the need for a stack most implementations will use one. But the concept itself is not useful for thinking about C++ objects. You should look up &quot;Automatic/Static/Dynamic&quot; storage duration.

I have no idea how to delete the nodes in a LinkedList once the LinkedList itself is deleted.

If you create something with new ou delete it with delete. I'll cover that below. When an object is destroyed (either manually or by going out of scope) its destructor will always be called.

I'm also not sure how to create a linkedlist without pointers.

I don't think I could do that either.

Also, should I potentially add an abstract class/or a c++ version of an interface for my LinkedList to follow.

In C++ we use this thing called &quot;Concepts&quot;. A linked list is an implementation of the &quot;Concept: Container&quot; there is no interface for a concept but rather a standard set of things that it should implement. https://en.wikipedia.org/wiki/Concepts_(C%2B%2B)
Thus allows for template to do &quot;Duck&quot; typing.
If it quacks like a duck then it can be used as a duck.
Basically templates allow you to use any type as long as the function you use are available for that type and it compiles it is said to follow the duck typing interface of the template.

and then I could use this same abstraction for, say, a stack?

Now there is already a type std::stack that underneath uses an object that implements the Concept of a SequenceContainer.
And defines these functions (As defined for a Sequenced Container).

back()
push_back()
pop_back()

See: https://en.cppreference.com/w/cpp/named_req/SequenceContainer
Code Review
Don't need to mention the template type inside the templates object.
template &lt;typename T&gt;
struct Node {
    Node(T value);
    T data;
    Node&lt;T&gt; *next;  // That &lt;T&gt; is not needed here.
                    // The compiler assumes its a T unless you
                    // manually specify otherwise.
};


The * and &amp; and &amp;&amp; are part of the type infomration. So in C++ (unlike C) they are usually placed next to the type information not the member.
    Node*   next;   // Next has a type Node* (or Node Pointer).


Don't use this-&gt;
template &lt;typename T&gt;
Node&lt;T&gt;::Node(T value){
    this-&gt;data = value;
    this-&gt;next = nullptr;
}

The only time you need to use this-&gt; is when you have a local variable that shadows a member variable. If you have a shadowing problem them the compiler can not tell when you accidently leave off the this-&gt; and thus shadowing leads to hard to detect bugs.
So it is better to use unique names and get the compiler to warn you when you have a shodowed variable. Then your naming of variables becomes better and you don't accidently use the wrong variable.

For short function put them in the class definition.
template &lt;typename T&gt;
Node&lt;T&gt;::Node(T value){
    this-&gt;data = value;
    this-&gt;next = nullptr;
}

Thats short and because you don't need the template &lt;typename T&gt; in the class it becomes even shorter.

Prefer to use the initializer list over constructor code.
When an object is created all its members are initialized in the order declared in the class. If there are no members initialized in the initializer list they are zero or default initialized depending on the exact declaration.
But if you are setting the values in the constructor code this means you are re-setting them in the code. For complex objects this means you are calling the operator= to redo work that was done in the initializer list.
template &lt;typename T&gt;
Node&lt;T&gt;::Node(T value)
    : data()            // You have an implicit initialization here
    , next()            // even if you don't do it yourself.
{
    data = value;       // If the above initialization did work
    next = nullptr;     // Then these two assignments become extra work.
}


Your copy constructor passes the data by value:
Node&lt;T&gt;::Node(T value)     // Pass by value causes a copy of value here.
    : data()               // Data is default constructed here.
                           // See above.
{ 
    this-&gt;data = value;    // Copy assignment causes another copy to be
                           // made here.

If you are keeping the data you need to copy it at some point but you don't need to copy it twice. So pass by const reference to avoid a copy.
Node&lt;T&gt;::Node(T const &amp; value)  // Pass by reference to avoid copy.
    : data(value)               // Construct data using the copy constructor.
{ 


In modern C++ we have the concept of move semantics. This allows an object to move expensive parts to another object if it is not going to be used again. Think of passing a huge array into an object. You may not want to copy it (as above). If you are not going to use the data again you want to move the object.
Node&lt;T&gt;::Node(T&amp;&amp; value)        // The double &amp;&amp; indicates it will bind
                                // To an r-value reference (something that can be moved)
    : data(std::move(value))    // You pass the T object to the move
                                // constructor of the type T to allow it to
                                // move itself.
{ 


Better declaration for Node:
template &lt;typename T&gt;
struct Node
{
    Node(T const&amp; value)
        : data(value)
        , next(nullptr)
    {}
    Node(T&amp;&amp; value)
        : data(std::move(value))
        , next(nullptr)
    {}
    T        data;
    Node*    next;
};


But even better news. Since the above is such a common pattern (Adding constructors and assignment operators like this). That this is done automatically for all types (unless you start manually defining them). So the above type can be defined simply by doing:
template &lt;typename T&gt;
struct Node
{
    T        data;
    Node*    next;
};

Note: Normally this is a bad thing if your object contains a pointer. BUT if we consider the Node as a non owning of the pointer it works. But you have to move the responsibility for owning onto the LinkedList class. So we will cover that below.

LinkedList is a class that contains an &quot;OWNED&quot; pointer (unlike Node). So this class you do need to specify a couple of methods or things go wrong.
Look up the rule of three.
If you don't specify them the compiler will automatically generate:

CopyConstructor LinkedList(LinkedList const&amp;);
CopyAssignment  LinkedList&amp; operator=(LinkedList const&amp;);
Destructor      ~LinkedList()

The constructors do a shallow copy and the destructor simply makes sure that all members are correctly destroyed. So in this case they do:
 LinkedList(LinkedList const&amp; copy)
     : head(copy.head)
 {}
 LinkedList&amp; operator=(LinkedList const&amp; copy)
 {
     head = copy.head;
     return *this;
 }
 ~LinkedList()
 {
     /* Nothing is done for pointers */
 }

So what you say. This does not affect me. This is true because currently you have not defined the destructor. But if you correctly define the destructor the constructors will cause problems.
 ~LinkedList()
 {
     deleteTheListPointedAtBy(head);  // This is what you should do.
 }

This causes problems because the above 2 constructors allow:
 {
      LinkedList&lt;int&gt;  a;
      a.push_back(1);
      LinkedList&lt;int&gt;  b(a);   // We are allowed to copy a like this.
                               // But both a and b have a head that points
                               // at the same value.

 } // At this point both a and b are destroyed.
   // Thus there destructors are called.
   // Thus they both try and delete all the same nodes.
   // which will cause problems.


As above in Node. You should think about move semantics. There are two methods added in C++11 that change the rule of three into the rule of 5.

MoveConstructor LinkedList(LinkedList&amp;&amp;);
MoveAssignment  LinkedList&amp; operator=(LinkedList&amp;&amp;);


As above use the initializer list:
template &lt;typename T&gt;
LinkedList&lt;T&gt;::LinkedList(){
    this-&gt;head = nullptr;
}


As above pass by const reference to avoid a copy.
template &lt;typename T&gt;
void LinkedList&lt;T&gt;::push_back(T data){


Same again pass by const reference:
template &lt;typename T&gt;
void LinkedList&lt;T&gt;::push_back(int index, T data){


This is good and looks correct.
    Node&lt;T&gt; *cursor = this-&gt;head;
    unsigned const position = index - 1;
    int i = 0;
    while (i &lt; position &amp;&amp; cursor-&gt;next != nullptr){
        cursor = cursor-&gt;next;
        i++;
    }
    Node&lt;T&gt; *nodeToInsert = new Node&lt;T&gt;(data);
    Node&lt;T&gt; *temp = cursor-&gt;next;
    cursor-&gt;next = nodeToInsert;
    nodeToInsert-&gt;next = temp;

But we could simplify it a bit:
    Node* cursor = head;
    for(int loop = 0;loop &lt; position &amp;&amp; cursor-&gt;next !- nullptr;++loop,cursor = cursor-&gt;next)
    {}

    cursor-&gt;next = new Node&lt;T&gt;(data, cursor-&gt;next);


This also works well.
template &lt;typename T&gt;
void LinkedList&lt;T&gt;::print(){
    Node&lt;T&gt; *cursor = this-&gt;head;
    while(cursor != nullptr){
        std::cout &lt;&lt; cursor-&gt;data &lt;&lt; '\n';
        cursor = cursor-&gt;next;
    }
}

But you assume the output is std::cout. I would pass the output stream as a parameter (it can default to std::cout`). This makes the print much more flexable.
The print() method does not change the state of the object so it should be marked const.
In C++ we normally use operator&lt;&lt; to output stuff. So you may want to write that function so it looks more natural. It can simply call the print method.
template &lt;typename T&gt;
void LinkedList&lt;T&gt;::print(std::ostream&amp; str) const
{
    Node&lt;T&gt; *cursor = this-&gt;head;
    while(cursor != nullptr){
        str &lt;&lt; cursor-&gt;data &lt;&lt; '\n';
        cursor = cursor-&gt;next;
    }
}
template&lt;typename T&gt;
std::ostream&amp; operator&lt;&lt;(std::ostream&amp; str, LinkedList&lt;T&gt; const&amp; list)
{
    list.print(str);
    return str;
}


Your missing destructor:
template&lt;typename T&gt;
~LinkedList()
{
    Node&lt;T&gt;*   old;
    while (head) {
        old = head;
        head = head-&gt;next;
        delete old;
    }
 }

Alternative Implementation:
template &lt;typename T&gt;
class LinkedList
{
    // Make Node a private member of the Linked List.
    struct Node
    {
        T      data;
        Node*  next;
    };
    Node*      head;
    Node*      tail;

    public:
        ~LinkedList() {deleteList();}

        LinkedList()
            : head(nullptr)
            , tail(nullptr)
        {}

        LinkedList(LinkedList const&amp; copy)
            : LinkedList()
        {
            for(Node* other = copy-&gt;head; head; head = head-&gt;next) {
                push_back(other-&gt;data);
            }
        }
        LinkedList operator=(LinkedList const&amp; copy)
        {
            LinkedList   tmp(copy);  // See Copy and Swap Idiom
            swap(tmp);
            return *this;
        }

        LinkedList(LinkedList&amp;&amp; move) noexcept
            : LinkedList()
        {
            swap(move);
        }
        LinkedList operator=(LinkedList&amp;&amp; move) noexcept
        {
            swap(move);
            return *this;
        }

        void swap(LinkedList&amp; other) noexcept
        {
            using std::swap;
            swap(head, other.head);
            swap(tail, other.tail);
        }
        friend void swap(LinkedList&amp; lhs, LinkedList&amp; rhs) noexcept
        {
            lhs.swap(rhs);
        }

        void push_back(T const&amp; data)
        {
            pushbackNode(new Node{data, nullptr});
        }
        void push_back(T&amp;&amp; data)
        {
            pushbackNode(new Node{std::move(data), nullptr});
        }

        void push_back(int index, T const&amp; data); 
        void push_back(int index, T&amp;&amp; data);

        void print() const;
        friend std::ostream&amp; operator&lt;&lt;(std::ostream&amp; str, LinkedList const&amp; data) {
            data.print(str);
            return str;
        }
    private:
        void deleteList()
        {
            Node*  old;
            while (head) {
                old  = head;
                head = head-&gt;next;
                delete old;
            }
        } 
        void pushbackNode(Node* node)
        {
            if (tail) {
                tail-&gt;next = node;
                tail = tail-&gt;next;
            }
            else {
                head = tail = node;
            }
        }
};