The best way to do this is (as you said) to just use the definition of periodic boundary conditions and set up your equations correctly from the start using the fact that $u(0)=u(1)$. In fact, even more strongly, periodic boundary conditions identify $x=0$ with $x=1$. For this reason, you should only have one of these points in your solution domain. An open interval does not make sense when using periodic boundary conditions since there is no boundary.
This fact means that you should not place a point at $x=1$ since it is the same as $x=0$. Discretizing with $N+1$ points, you then use the fact that by definition, the point to the left of $x_0$ is $x_N$ and the point to the right of $x_N$ is $x_0$.
Your PDE can then be discretized in space as
$$
\frac{\partial}{\partial t}
\left[\begin{array}{c}
x_0 \\
x_1 \\
\vdots \\
x_N
\end{array}\right]
=
\frac{1}{\Delta x^2}
\left[\begin{array}{c}
x_N - 2x_0 + x_1 \\
x_0 - 2x_1 + x_2 \\
\vdots \\
x_{N-1} - 2x_N + x_0
\end{array}\right]
$$
This can be written in matrix form as
$$
\frac{\partial}{\partial t}\vec{x}
=
\frac{1}{\Delta x^2} \mathbf{A} \vec{x}
$$
where
$$
\mathbf{A} =
\left[\begin{array}{c}
-2 & 1 & 0 & \cdots & 0 & 1 \\
1 & -2 & 1 & 0 & \cdots & 0 \\
& \ddots & \ddots & \ddots \\
&& \ddots & \ddots & \ddots \\
0 & \cdots & 0 & 1 & -2 & 1 \\
1 & 0 & \cdots & 0 & 1 & -2
\end{array}\right].
$$
Of course there is no need to actually create or store this matrix. The finite differences should be computed on the fly, taking care to handle the first and last points specially as needed.
As a simple example, the following MATLAB script solves
$$
\partial_t u = \partial_{xx}u + b(t,x)
$$
with periodic boundary conditions on the domain $x\in[-1,1)$. The manufactured solution $u_\text{Ref}(t,x) = \exp(-t)\cos(5\pi x)$ is used, meaning $b(t,x) = (25\pi^2-1)\exp(-t)\cos(5\pi x)$. I used forward Euler time discretization for simplicity and computed the solution both with and without forming the matrix. The results are shown below.
clear
% Solve: u_t = u_xx + b
% with periodic boundary conditions
% analytical solution:
uRef = @(t,x) exp(-t)*cos(5*pi*x);
b = @(t,x) (25*pi^2-1)*exp(-t)*cos(5*pi*x);
% grid
N = 30;
x(:,1) = linspace(-1,1,N+1);
% leave off 1 point so initial condition is periodic
% (doesn't have a duplicate point)
x(end) = [];
uWithMatrix = uRef(0,x);
uNoMatrix = uRef(0,x);
dx = diff(x(1:2));
dt = dx.^2/2;
%Iteration matrix:
e = ones(N,1);
A = spdiags([e -2*e e], -1:1, N, N);
A(N,1) = 1;
A(1,N) = 1;
A = A/dx^2;
%indices (left, center, right) for second order centered difference
iLeft = [numel(x), 1:numel(x)-1]';
iCenter = (1:numel(x))';
iRight = [2:numel(x), 1]';
%plot
figure(1)
clf
hold on
h0=plot(x,uRef(0,x),'k--','linewidth',2);
h1=plot(x,uWithMatrix);
h2=plot(x,uNoMatrix,'o');
ylim([-1.2, 1.2])
legend('Analytical solution','Matrix solution','Matrix-free solution')
ht = title(sprintf('Time t = %0.2f',0));
xlabel('x')
ylabel('u')
drawnow
for t = 0:dt:1
uWithMatrix = uWithMatrix + dt*( A*uWithMatrix + b(t,x) );
uNoMatrix = uNoMatrix + dt*( ( uNoMatrix(iLeft) ...
- 2*uNoMatrix(iCenter) ...
+ uNoMatrix(iRight) )/dx^2 ...
+ b(t,x) );
set(h0,'ydata',uRef(t,x))
set(h1,'ydata',uWithMatrix)
set(h2,'ydata',uNoMatrix)
set(ht,'String',sprintf('Time t = %0.2f',t))
drawnow
end